//
//  MBIMAuthToken.m
//  MBIMAuthToken
//
//  Created by James Magahern on 7/6/21.
//  Copyright © 2021 James Magahern. All rights reserved.
//

#import "MBIMAuthToken.h"
#import <CommonCrypto/CommonHMAC.h>

#define HOUR 3600
#define DAY (24*HOUR)

static const NSTimeInterval ExpirationTime = 15 * DAY;
static const char *SecretKey = "709E7CD8-4983-4D5F-B7BF-8B1C6341D2DB";

static NSString *const kUsernamePayloadKey = @"user";
static NSString *const kIssuerPayloadKey = @"iss";
static NSString *const kExpirationDatePayloadKey = @"exp";

static NSString *const kIssuerPayloadValue = @"kordophone";

@interface MBIMAuthToken ()
@property (nonatomic, copy) NSString *username;
@property (nonatomic, copy) NSString *jwtToken;
@property (nonatomic, copy) NSDate *expirationDate;

// JWT Payload data
@property (nonatomic, copy) NSString *headerString;
@property (nonatomic, copy) NSString *payloadString;
@property (nonatomic, copy) NSData   *signatureData;
@end

@implementation MBIMAuthToken

- (instancetype)initWithUsername:(NSString *)username
{
    self = [super init];
    if (self) {
        self.username = username;
        self.expirationDate = [NSDate dateWithTimeIntervalSinceNow:ExpirationTime];
    }
    
    return self;
}

- (instancetype)initWithTokenString:(NSString *)tokenString
{
    NSArray<NSString *> *components = [tokenString componentsSeparatedByString:@"."];
    if (components.count != 3) {
        return nil;
    }
    
    NSString *header = components[0];
    NSString *payload = components[1];
    NSString *signature = components[2];
    
    NSData *payloadData = [[NSData alloc] initWithBase64EncodedString:payload options:0];
    NSDictionary *decodedPayload = [NSJSONSerialization JSONObjectWithData:payloadData options:0 error:nil];
    if (!decodedPayload) {
        return nil;
    }
    
    if (![decodedPayload[kIssuerPayloadKey] isEqualToString:@"kordophone"]) {
        return nil;
    }
    
    self = [super init];
    if (self) {
        _headerString = header;
        _payloadString = payload;
        _signatureData = [[NSData alloc] initWithBase64EncodedString:signature options:0];
        
        _username = decodedPayload[kUsernamePayloadKey];
        
        NSTimeInterval expirationDate = [decodedPayload[kExpirationDatePayloadKey] floatValue];
        _expirationDate = [NSDate dateWithTimeIntervalSince1970:expirationDate];
    }
    
    return self;
}

- (NSUInteger)hash
{
    return (_username.hash ^ _expirationDate.hash);
}

- (NSString *)jwtToken
{
    if (!_jwtToken) {
        NSDictionary *header = @{
            @"alg" : @"HS256",
            @"typ" : @"jwt"
        };
        
        NSData *headerData = [NSJSONSerialization dataWithJSONObject:header options:0 error:nil];
        NSString *headerStr = [headerData base64EncodedStringWithOptions:0];
        
        NSInteger expirationDate = [_expirationDate timeIntervalSince1970];
        NSDictionary *payload = @{
            kUsernamePayloadKey        : _username,
            kIssuerPayloadKey          : kIssuerPayloadValue,
            kExpirationDatePayloadKey  : [NSString stringWithFormat:@"%ld", expirationDate]
        };
        NSData *payloadData = [NSJSONSerialization dataWithJSONObject:payload options:0 error:nil];
        NSString *payloadStr = [payloadData base64EncodedStringWithOptions:0];
        
        NSString *jwtDataStr = [NSString stringWithFormat:@"%@.%@", headerStr, payloadStr];
        NSData *jwtData = [jwtDataStr dataUsingEncoding:NSASCIIStringEncoding];
        
        unsigned char signature[CC_SHA256_DIGEST_LENGTH];
        CCHmac(kCCHmacAlgSHA256, SecretKey, sizeof(SecretKey), jwtData.bytes, jwtData.length, signature);
        
        NSData *signatureData = [NSData dataWithBytes:signature length:CC_SHA256_DIGEST_LENGTH];
        NSString *signatureStr = [signatureData base64EncodedStringWithOptions:0];
        
        _jwtToken = [NSString stringWithFormat:@"%@.%@", jwtDataStr, signatureStr];
    }
    
    return _jwtToken;
}

- (BOOL)isValid
{
    // Verify expiration date
    BOOL expirationDateValid = [_expirationDate timeIntervalSinceNow] > 0;
    if (!expirationDateValid) {
        MBIMLogInfo(@"Auth token expired.");
        return NO;
    }
    
    // Verify signature
    NSString *verificationDataStr = [NSString stringWithFormat:@"%@.%@", _headerString, _payloadString];
    NSData *verificationData = [verificationDataStr dataUsingEncoding:NSASCIIStringEncoding];
    
    unsigned char computedSignature[CC_SHA256_DIGEST_LENGTH];
    CCHmac(kCCHmacAlgSHA256, SecretKey, sizeof(SecretKey), verificationData.bytes, verificationData.length, computedSignature);
    
    NSData *computedSignatureData = [NSData dataWithBytes:computedSignature length:CC_SHA256_DIGEST_LENGTH];
    if (![computedSignatureData isEqualToData:_signatureData]) {
        MBIMLogInfo(@"Auth token signature verification failed.");
        return NO;
    }
    
    return YES;
}

@end
