import Foundation
import Security
import CryptoKit
import UIKit
import React

@objc(DeviceBinding)
class DeviceBinding: NSObject {

    private let defaultsDeviceIdKey = "com.devicebinding.deviceId"

    // MARK: - Generate Key Pair (Secure Enclave P-256)

    @objc(generateKeyPair:resolver:rejecter:)
    func generateKeyPair(_ alias: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
        let tag = "com.devicebinding.\(alias)".data(using: .utf8)!

        deleteKey(tag: tag)

        var accessError: Unmanaged<CFError>?
        guard let accessControl = SecAccessControlCreateWithFlags(
            kCFAllocatorDefault,
            kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
            [.privateKeyUsage],
            &accessError
        ) else {
            reject("KEY_GEN_ERROR", "Failed to create access control: \(String(describing: accessError))", nil)
            return
        }

        let attributes: [String: Any] = [
            kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
            kSecAttrKeySizeInBits as String: 256,
            kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
            kSecPrivateKeyAttrs as String: [
                kSecAttrIsPermanent as String: true,
                kSecAttrApplicationTag as String: tag,
                kSecAttrAccessControl as String: accessControl
            ] as [String: Any]
        ]

        var error: Unmanaged<CFError>?
        guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
            let fallbackAttributes: [String: Any] = [
                kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
                kSecAttrKeySizeInBits as String: 256,
                kSecPrivateKeyAttrs as String: [
                    kSecAttrIsPermanent as String: true,
                    kSecAttrApplicationTag as String: tag,
                    kSecAttrAccessControl as String: accessControl
                ] as [String: Any]
            ]

            var fallbackError: Unmanaged<CFError>?
            guard let fallbackKey = SecKeyCreateRandomKey(fallbackAttributes as CFDictionary, &fallbackError) else {
                reject("KEY_GEN_ERROR", "Failed to generate key pair: \(String(describing: fallbackError))", nil)
                return
            }

            resolveKeyPair(fallbackKey, resolve: resolve, reject: reject)
            return
        }

        resolveKeyPair(privateKey, resolve: resolve, reject: reject)
    }

    private func resolveKeyPair(_ privateKey: SecKey, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
            reject("KEY_GEN_ERROR", "Failed to extract public key", nil)
            return
        }

        var exportError: Unmanaged<CFError>?
        guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &exportError) as Data? else {
            reject("KEY_EXPORT_ERROR", "Failed to export public key: \(String(describing: exportError))", nil)
            return
        }

        let spkiHeader: [UInt8] = [
            0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86,
            0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A,
            0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03,
            0x42, 0x00
        ]
        var spkiData = Data(spkiHeader)
        spkiData.append(publicKeyData)

        let publicKeyBase64 = spkiData.base64EncodedString()

        var attestation = ""
        if #available(iOS 14.0, *) {
            attestation = "secure_enclave_ios"
        }

        resolve([
            "publicKey": publicKeyBase64,
            "keyAttestation": attestation
        ])
    }

    // MARK: - Get Public Key

    @objc(getPublicKey:resolver:rejecter:)
    func getPublicKey(_ alias: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
        let tag = "com.devicebinding.\(alias)".data(using: .utf8)!

        let query: [String: Any] = [
            kSecClass as String: kSecClassKey,
            kSecAttrApplicationTag as String: tag,
            kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
            kSecReturnRef as String: true
        ]

        var item: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &item)

        guard status == errSecSuccess, let privateKey = item else {
            reject("KEY_NOT_FOUND", "No key pair found for alias: \(alias)", nil)
            return
        }

        let secKey = privateKey as! SecKey
        guard let publicKey = SecKeyCopyPublicKey(secKey) else {
            reject("KEY_ERROR", "Failed to extract public key", nil)
            return
        }

        var error: Unmanaged<CFError>?
        guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
            reject("KEY_EXPORT_ERROR", "Failed to export public key", nil)
            return
        }

        let spkiHeader: [UInt8] = [
            0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86,
            0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A,
            0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03,
            0x42, 0x00
        ]
        var spkiData = Data(spkiHeader)
        spkiData.append(publicKeyData)

        resolve(spkiData.base64EncodedString())
    }

    // MARK: - Sign Challenge

    @objc(signChallenge:challenge:resolver:rejecter:)
    func signChallenge(_ alias: String, challenge: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
        let tag = "com.devicebinding.\(alias)".data(using: .utf8)!

        let query: [String: Any] = [
            kSecClass as String: kSecClassKey,
            kSecAttrApplicationTag as String: tag,
            kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
            kSecReturnRef as String: true
        ]

        var item: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &item)

        guard status == errSecSuccess, let privateKey = item else {
            reject("KEY_NOT_FOUND", "No key pair found for alias: \(alias)", nil)
            return
        }

        let secKey = privateKey as! SecKey

        guard let challengeData = challenge.data(using: .utf8) else {
            reject("SIGN_ERROR", "Failed to encode challenge", nil)
            return
        }

        var signError: Unmanaged<CFError>?
        guard let signature = SecKeyCreateSignature(
            secKey,
            .ecdsaSignatureMessageX962SHA256,
            challengeData as CFData,
            &signError
        ) as Data? else {
            reject("SIGN_ERROR", "Failed to sign challenge: \(String(describing: signError))", nil)
            return
        }

        resolve(signature.base64EncodedString())
    }

    // MARK: - Delete Key Pair

    @objc(deleteKeyPair:resolver:rejecter:)
    func deleteKeyPair(_ alias: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
        let tag = "com.devicebinding.\(alias)".data(using: .utf8)!
        let deleted = deleteKey(tag: tag)
        resolve(deleted)
    }

    @discardableResult
    private func deleteKey(tag: Data) -> Bool {
        let query: [String: Any] = [
            kSecClass as String: kSecClassKey,
            kSecAttrApplicationTag as String: tag
        ]
        let status = SecItemDelete(query as CFDictionary)
        return status == errSecSuccess
    }

    // MARK: - Has Key Pair

    @objc(hasKeyPair:resolver:rejecter:)
    func hasKeyPair(_ alias: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
        let tag = "com.devicebinding.\(alias)".data(using: .utf8)!

        let query: [String: Any] = [
            kSecClass as String: kSecClassKey,
            kSecAttrApplicationTag as String: tag,
            kSecReturnRef as String: false
        ]

        let status = SecItemCopyMatching(query as CFDictionary, nil)
        resolve(status == errSecSuccess)
    }

    // MARK: - StrongBox Available (iOS always returns true for Secure Enclave on A7+)

    @objc(isStrongBoxAvailable:rejecter:)
    func isStrongBoxAvailable(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
        resolve(true)
    }

    // MARK: - Device ID

    @objc(getDeviceId:rejecter:)
    func getDeviceId(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
        if let existingId = UserDefaults.standard.string(forKey: defaultsDeviceIdKey) {
            resolve(existingId)
            return
        }

        let newId = UUID().uuidString.lowercased()
        UserDefaults.standard.set(newId, forKey: defaultsDeviceIdKey)
        resolve(newId)
    }

    @objc(resetDeviceId:rejecter:)
    func resetDeviceId(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
        let newId = UUID().uuidString.lowercased()
        UserDefaults.standard.set(newId, forKey: defaultsDeviceIdKey)
        resolve(newId)
    }

    // MARK: - Module Config

    @objc static func requiresMainQueueSetup() -> Bool {
        return false
    }
}
