import Foundation
import CommonCrypto

class EncryptionHelper {
    enum EncryptionError: Error {
        case keyGenerationFailed
        case encryptionFailed
        case invalidInputData
    }

    static func encryptHMACAES(plainText: String, secretKey: String) throws -> String {
        // Generate AES key from HMAC-SHA256 of the secret key
        guard let keyData = secretKey.data(using: .utf8),
              let hmacKey = generateHMACKey(from: keyData) else {
            throw EncryptionError.keyGenerationFailed
        }

        // Generate random IV (16 bytes)
        let iv = generateRandomIV()

        // Prepare the plain text data
        guard let dataToEncrypt = plainText.data(using: .utf8) else {
            throw EncryptionError.invalidInputData
        }

        // Encrypt the data
        guard let encryptedData = try? encrypt(data: dataToEncrypt, key: hmacKey, iv: iv) else {
            throw EncryptionError.encryptionFailed
        }

        // Combine IV and encrypted data
        let combined = iv + encryptedData

        // Convert to base64
        return combined.base64EncodedString()
    }

    private static func generateHMACKey(from keyData: Data) -> Data? {
        var hmacContext = CCHmacContext()
        let algorithm = CCHmacAlgorithm(kCCHmacAlgSHA256)
        let digestLength = Int(CC_SHA256_DIGEST_LENGTH)

        keyData.withUnsafeBytes { keyPtr in
            CCHmacInit(&hmacContext, algorithm, keyPtr.baseAddress, keyData.count)
            CCHmacUpdate(&hmacContext, keyPtr.baseAddress, keyData.count)
        }

        var hmacOut = [UInt8](repeating: 0, count: digestLength)
        CCHmacFinal(&hmacContext, &hmacOut)

        // Take first 32 bytes (64 hex characters) for AES-256
        return Data(hmacOut).prefix(32)
    }

    private static func generateRandomIV() -> Data {
        var iv = Data(count: kCCBlockSizeAES128)
        let result = iv.withUnsafeMutableBytes { bytes in
            SecRandomCopyBytes(kSecRandomDefault, kCCBlockSizeAES128, bytes.baseAddress!)
        }
        assert(result == errSecSuccess)
        return iv
    }

    private static func encrypt(data: Data, key: Data, iv: Data) throws -> Data {
        let bufferSize = data.count + kCCBlockSizeAES128
        var buffer = Data(count: bufferSize)
        var numBytesEncrypted = 0

        let status = key.withUnsafeBytes { keyBytes in
            iv.withUnsafeBytes { ivBytes in
                data.withUnsafeBytes { dataBytes in
                    buffer.withUnsafeMutableBytes { bufferBytes in
                        CCCrypt(
                            CCOperation(kCCEncrypt),
                            CCAlgorithm(kCCAlgorithmAES),
                            CCOptions(kCCOptionPKCS7Padding),
                            keyBytes.baseAddress,
                            key.count,
                            ivBytes.baseAddress,
                            dataBytes.baseAddress,
                            data.count,
                            bufferBytes.baseAddress,
                            bufferSize,
                            &numBytesEncrypted
                        )
                    }
                }
            }
        }

        guard status == kCCSuccess else {
            throw EncryptionError.encryptionFailed
        }

        return buffer.prefix(numBytesEncrypted)
    }
    
    
    //MARK: decryt
    
    static func decryptHMACAES(encryptedBase64: String, secretKey: String) throws -> String {
            // Convert the base64-encoded string back to Data
            guard let combinedData = Data(base64Encoded: encryptedBase64) else {
                throw EncryptionError.invalidInputData
            }

            // Extract the IV (first 16 bytes) and the encrypted data (remaining bytes)
            let ivSize = kCCBlockSizeAES128
            guard combinedData.count > ivSize else {
                throw EncryptionError.invalidInputData
            }

            let iv = combinedData.prefix(ivSize)
            let encryptedData = combinedData.suffix(from: ivSize)

            // Generate the AES key from HMAC-SHA256 of the secret key
            guard let keyData = secretKey.data(using: .utf8),
                  let hmacKey = generateHMACKey(from: keyData) else {
                throw EncryptionError.keyGenerationFailed
            }

            // Decrypt the encrypted data
            guard let decryptedData = try? decrypt(data: encryptedData, key: hmacKey, iv: iv) else {
                throw EncryptionError.encryptionFailed
            }

            // Convert decrypted data to a plain text string
            guard let plainText = String(data: decryptedData, encoding: .utf8) else {
                throw EncryptionError.invalidInputData
            }

            return plainText
        }

        private static func decrypt(data: Data, key: Data, iv: Data) throws -> Data {
            let bufferSize = data.count + kCCBlockSizeAES128
            var buffer = Data(count: bufferSize)
            var numBytesDecrypted = 0

            let status = key.withUnsafeBytes { keyBytes in
                iv.withUnsafeBytes { ivBytes in
                    data.withUnsafeBytes { dataBytes in
                        buffer.withUnsafeMutableBytes { bufferBytes in
                            CCCrypt(
                                CCOperation(kCCDecrypt),
                                CCAlgorithm(kCCAlgorithmAES),
                                CCOptions(kCCOptionPKCS7Padding),
                                keyBytes.baseAddress,
                                key.count,
                                ivBytes.baseAddress,
                                dataBytes.baseAddress,
                                data.count,
                                bufferBytes.baseAddress,
                                bufferSize,
                                &numBytesDecrypted
                            )
                        }
                    }
                }
            }

            guard status == kCCSuccess else {
                throw EncryptionError.encryptionFailed
            }

            return buffer.prefix(numBytesDecrypted)
        }
}

