#import "DesMachine.h"
#import <CommonCrypto/CommonCryptor.h>

// Mode constants
static NSString *const MODE_ECB = @"ECB";
static NSString *const MODE_CBC = @"CBC";
static NSString *const MODE_CFB = @"CFB";
static NSString *const MODE_OFB = @"OFB";
static NSString *const MODE_CTR = @"CTR";

// Padding constants
static NSString *const PADDING_PKCS7 = @"PKCS7";
static NSString *const PADDING_ISO10126 = @"ISO10126";
static NSString *const PADDING_ZERO = @"ZERO";
static NSString *const PADDING_NONE = @"NONE";

// Output format constants
static NSString *const FORMAT_BASE64 = @"BASE64";
static NSString *const FORMAT_HEX = @"HEX";

@implementation DesMachine

#pragma mark - TurboModule

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
    (const facebook::react::ObjCTurboModule::InitParams &)params
{
    return std::make_shared<facebook::react::NativeDesMachineSpecJSI>(params);
}

+ (NSString *)moduleName
{
    return @"DesMachine";
}

#pragma mark - NativeDesMachineSpec Methods

/**
 * Encrypt the provided plaintext using DES algorithm.
 * Stateless operation - all parameters are passed with each call.
 *
 * @param params DesMachineParams containing:
 *   - key: NSString (required, minimum 8 characters)
 *   - mode: NSString (optional, default: ECB) - ECB, CBC, CFB, OFB, CTR
 *   - padding: NSString (optional, default: PKCS7) - PKCS7, ISO10126, ZERO, NONE
 *   - outputFormat: NSString (optional, default: BASE64) - BASE64, HEX
 * @param text The plaintext to encrypt
 * @return Encrypted string in the configured output format (BASE64 or HEX)
 */
- (NSString *)encrypt:(JS::NativeDesMachine::DesMachineParams &)params
                 text:(NSString *)text {
    // Parse parameters
    NSString *key = params.key();
    if (!key || key.length < 8) {
        @throw [NSException exceptionWithName:@"InvalidKeyException"
                                       reason:@"DES key must be at least 8 characters long"
                                     userInfo:nil];
    }
    
    NSString *iv = params.iv();
    if (iv && iv.length != 8) {
        @throw [NSException exceptionWithName:@"InvalidIVException"
                                       reason:@"IV must be exactly 8 characters long"
                                     userInfo:nil];
    }
    
    NSString *mode = params.mode() ?: MODE_ECB;
    NSString *padding = params.padding() ?: PADDING_PKCS7;
    NSString *outputFormat = params.outputFormat() ?: FORMAT_BASE64;
    
    // Prepare key and IV
    NSData *keyData = [self prepareKey:key];
    NSData *ivData = nil;
    if (![mode isEqualToString:MODE_ECB]) {
        ivData = iv ? [self prepareIV:iv] : keyData;
    }
    
    // Encrypt
    NSData *inputData = [text dataUsingEncoding:NSUTF8StringEncoding];
    NSData *paddedData = [self applyPadding:inputData withPadding:padding];
    NSData *encryptedData = [self performDESOperation:kCCEncrypt
                                               onData:paddedData
                                              withKey:keyData
                                                   iv:ivData
                                                 mode:mode
                                              padding:padding];
    
    return [self formatOutput:encryptedData withFormat:outputFormat];
}

/**
 * Decrypt the provided ciphertext using DES algorithm.
 * Stateless operation - all parameters are passed with each call.
 *
 * @param params DesMachineParams containing:
 *   - key: NSString (required, minimum 8 characters)
 *   - mode: NSString (optional, default: ECB) - ECB, CBC, CFB, OFB, CTR
 *   - padding: NSString (optional, default: PKCS7) - PKCS7, ISO10126, ZERO, NONE
 *   - outputFormat: NSString (optional, default: BASE64) - BASE64, HEX
 * @param text Encrypted string in the configured format (BASE64 or HEX)
 * @return Decrypted plaintext
 */
- (NSString *)decrypt:(JS::NativeDesMachine::DesMachineParams &)params
                 text:(NSString *)text {
    // Parse parameters
    NSString *key = params.key();
    if (!key || key.length < 8) {
        @throw [NSException exceptionWithName:@"InvalidKeyException"
                                       reason:@"DES key must be at least 8 characters long"
                                     userInfo:nil];
    }
    
    NSString *iv = params.iv();
    if (iv && iv.length != 8) {
        @throw [NSException exceptionWithName:@"InvalidIVException"
                                       reason:@"IV must be exactly 8 characters long"
                                     userInfo:nil];
    }
    
    NSString *mode = params.mode() ?: MODE_ECB;
    NSString *padding = params.padding() ?: PADDING_PKCS7;
    NSString *outputFormat = params.outputFormat() ?: FORMAT_BASE64;
    
    // Prepare key and IV
    NSData *keyData = [self prepareKey:key];
    NSData *ivData = nil;
    if (![mode isEqualToString:MODE_ECB]) {
        ivData = iv ? [self prepareIV:iv] : keyData;
    }
    
    // Decrypt
    NSData *encryptedData = [self parseInput:text withFormat:outputFormat];
    NSData *decryptedData = [self performDESOperation:kCCDecrypt
                                               onData:encryptedData
                                              withKey:keyData
                                                   iv:ivData
                                                 mode:mode
                                              padding:padding];
    NSData *unpaddedData = [self removePadding:decryptedData withPadding:padding];
    
    return [[NSString alloc] initWithData:unpaddedData encoding:NSUTF8StringEncoding];
}

#pragma mark - Key and IV Preparation

- (NSData *)prepareKey:(NSString *)key {
    NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
    NSData *secretKey = [keyData subdataWithRange:NSMakeRange(0, MIN(8, keyData.length))];
    
    // Pad key to 8 bytes if needed
    if (secretKey.length < 8) {
        NSMutableData *paddedKey = [NSMutableData dataWithData:secretKey];
        [paddedKey setLength:8];
        return paddedKey;
    }
    
    return secretKey;
}

- (NSData *)prepareIV:(NSString *)iv {
    NSData *ivData = [iv dataUsingEncoding:NSUTF8StringEncoding];
    // IV must be exactly 8 bytes for DES
    return [ivData subdataWithRange:NSMakeRange(0, MIN(8, ivData.length))];
}

#pragma mark - DES Operations

- (NSData *)performDESOperation:(CCOperation)operation
                         onData:(NSData *)data
                        withKey:(NSData *)key
                             iv:(NSData *)iv
                           mode:(NSString *)mode
                        padding:(NSString *)padding {
    CCOptions options = [self getCCOptionsForMode:mode padding:padding];
    
    size_t bufferSize = data.length + kCCBlockSizeDES;
    void *buffer = malloc(bufferSize);
    size_t numBytesProcessed = 0;
    
    CCCryptorStatus status = CCCrypt(
        operation,
        kCCAlgorithmDES,
        options,
        key.bytes,
        kCCKeySizeDES,
        iv.bytes,
        data.bytes,
        data.length,
        buffer,
        bufferSize,
        &numBytesProcessed
    );
    
    if (status != kCCSuccess) {
        free(buffer);
        @throw [NSException exceptionWithName:@"CryptoException"
                                       reason:[NSString stringWithFormat:@"DES operation failed with status: %d", status]
                                     userInfo:nil];
    }
    
    NSData *result = [NSData dataWithBytes:buffer length:numBytesProcessed];
    free(buffer);
    
    return result;
}

- (CCOptions)getCCOptionsForMode:(NSString *)mode padding:(NSString *)padding {
    CCOptions options = 0;
    
    // ECB mode
    if ([mode isEqualToString:MODE_ECB]) {
        options |= kCCOptionECBMode;
    }
    
    // Padding
    if ([padding isEqualToString:PADDING_PKCS7]) {
        options |= kCCOptionPKCS7Padding;
    }
    // For other padding types, we handle manually (no padding flag = NoPadding)
    
    return options;
}

#pragma mark - Padding

- (NSData *)applyPadding:(NSData *)data withPadding:(NSString *)padding {
    if ([padding isEqualToString:PADDING_PKCS7]) {
        // PKCS7 is handled by CommonCrypto
        return data;
    }
    
    NSUInteger blockSize = kCCBlockSizeDES;
    NSUInteger paddingLength = blockSize - (data.length % blockSize);
    
    if (paddingLength == blockSize && [padding isEqualToString:PADDING_NONE]) {
        return data;
    }
    
    NSMutableData *paddedData = [NSMutableData dataWithData:data];
    
    if ([padding isEqualToString:PADDING_ZERO] || [padding isEqualToString:PADDING_NONE]) {
        // Zero padding
        [paddedData setLength:data.length + paddingLength];
    } else if ([padding isEqualToString:PADDING_ISO10126]) {
        // ISO10126: random bytes followed by padding length
        uint8_t randomBytes[paddingLength];
        for (NSUInteger i = 0; i < paddingLength - 1; i++) {
            randomBytes[i] = arc4random_uniform(256);
        }
        randomBytes[paddingLength - 1] = (uint8_t)paddingLength;
        [paddedData appendBytes:randomBytes length:paddingLength];
    }
    
    return paddedData;
}

- (NSData *)removePadding:(NSData *)data withPadding:(NSString *)padding {
    if ([padding isEqualToString:PADDING_PKCS7]) {
        // PKCS7 is handled by CommonCrypto
        return data;
    }
    
    if ([padding isEqualToString:PADDING_ZERO]) {
        // Find last non-zero byte
        const uint8_t *bytes = (const uint8_t *)data.bytes;
        NSInteger lastIndex = data.length - 1;
        while (lastIndex >= 0 && bytes[lastIndex] == 0) {
            lastIndex--;
        }
        return [data subdataWithRange:NSMakeRange(0, lastIndex + 1)];
    }
    
    if ([padding isEqualToString:PADDING_ISO10126]) {
        // Last byte indicates padding length
        const uint8_t *bytes = (const uint8_t *)data.bytes;
        uint8_t paddingLength = bytes[data.length - 1];
        if (paddingLength > 0 && paddingLength <= kCCBlockSizeDES) {
            return [data subdataWithRange:NSMakeRange(0, data.length - paddingLength)];
        }
    }
    
    return data;
}

#pragma mark - Output Formatting

- (NSString *)formatOutput:(NSData *)data withFormat:(NSString *)format {
    if ([format isEqualToString:FORMAT_HEX]) {
        NSMutableString *hexString = [NSMutableString stringWithCapacity:data.length * 2];
        const uint8_t *bytes = (const uint8_t *)data.bytes;
        for (NSUInteger i = 0; i < data.length; i++) {
            [hexString appendFormat:@"%02x", bytes[i]];
        }
        return hexString;
    }
    
    // Default: BASE64
    return [data base64EncodedStringWithOptions:0];
}

- (NSData *)parseInput:(NSString *)text withFormat:(NSString *)format {
    if ([format isEqualToString:FORMAT_HEX]) {
        NSMutableData *data = [NSMutableData dataWithCapacity:text.length / 2];
        for (NSUInteger i = 0; i < text.length; i += 2) {
            NSString *hexByte = [text substringWithRange:NSMakeRange(i, 2)];
            unsigned int byteValue;
            [[NSScanner scannerWithString:hexByte] scanHexInt:&byteValue];
            uint8_t byte = (uint8_t)byteValue;
            [data appendBytes:&byte length:1];
        }
        return data;
    }
    
    // Default: BASE64
    return [[NSData alloc] initWithBase64EncodedString:text options:0];
}

@end
