import Foundation
import os
import Security

/// C-iOS · A P-256 signing key that lives ENTIRELY inside SecKey /
/// Security.framework — never CryptoKit.
///
/// **`[FIX C-native, ClientTLS trap]`** — the enrollment path deliberately avoids
/// `CryptoKit.SecureEnclave.P256.Signing.PrivateKey` and `SecKeyCreateWithData`
/// over a Secure-Enclave token: either route mints a key the keychain cannot
/// later match to a stored certificate, so `SecItemCopyMatching(kSecClassIdentity)`
/// fails with `errSecItemNotFound (-25300)` and the whole mTLS identity silently
/// never assembles. Staying on `SecKeyCreateRandomKey` + `SecKeyCreateSignature`
/// keeps the private key a first-class, permanent keychain resident that the leaf
/// certificate binds to automatically.
public protocol P256HardwareKey: Sendable {
    /// The public key in ANSI X9.63 uncompressed form: `0x04 || X || Y`
    /// (65 bytes for P-256). This is exactly what wraps into a SubjectPublicKeyInfo.
    func publicKeyX963() throws -> Data

    /// ECDSA sign `message` over SHA-256, returning the X9.62 DER signature
    /// (`SEQUENCE { r INTEGER, s INTEGER }`) — the exact shape a PKCS#10
    /// `signature` BIT STRING and `verifyCsrPoPEc` expect. The digest is computed
    /// by the algorithm (`.ecdsaSignatureMessageX962SHA256`), so callers pass the
    /// raw message (the DER of `CertificationRequestInfo`), NOT a pre-hash.
    func sign(_ message: Data) throws -> Data
}

public enum SecureEnclaveKeyError: Error, Equatable, Sendable {
    /// The Secure Enclave is absent (Simulator) or the app lacks the entitlement.
    /// Carries the underlying `SecKeyCreateRandomKey` failure for diagnostics.
    case secureEnclaveUnavailable(String)
    /// Key generation failed for a reason other than SE-unavailability.
    case keyGenerationFailed(String)
    /// The public key could not be derived from the private key.
    case publicKeyUnavailable
    /// Exporting the public key to X9.63 bytes failed.
    case exportFailed(String)
    /// Signing failed (algorithm unsupported, user-presence denied, …).
    case signatureFailed(String)
    /// A keychain `SecItem*` lookup/delete failed with this status.
    case keychain(OSStatus)
}

/// A `SecKey`-backed P-256 key. The wrapped `SecKey` is a Secure-Enclave key in
/// production (via `SecureEnclaveKeyFactory.generate`) and an in-process software
/// key on Simulator/tests (via `.generateSoftware`) — both drive the SAME
/// `SecKeyCreateSignature` path, so the CSR encoder is exercised identically.
///
/// `@unchecked Sendable`: `SecKey` is an immutable, thread-safe CoreFoundation
/// handle once created; this wrapper only ever reads it.
public final class SecureEnclaveKey: P256HardwareKey, @unchecked Sendable {
    /// The (non-exportable, in production) private key. Never leaves the device.
    private let privateKey: SecKey

    /// Wrap an existing `SecKey`. Public so tests can inject a software P-256 key
    /// created via `SecKeyCreateRandomKey` without the SE token.
    public init(privateKey: SecKey) {
        self.privateKey = privateKey
    }

    public func publicKeyX963() throws -> Data {
        guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
            throw SecureEnclaveKeyError.publicKeyUnavailable
        }
        var error: Unmanaged<CFError>?
        guard let data = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
            throw SecureEnclaveKeyError.exportFailed(Self.describe(error))
        }
        return data
    }

    public func sign(_ message: Data) throws -> Data {
        var error: Unmanaged<CFError>?
        guard
            let signature = SecKeyCreateSignature(
                privateKey,
                .ecdsaSignatureMessageX962SHA256,
                message as CFData,
                &error
            ) as Data?
        else {
            throw SecureEnclaveKeyError.signatureFailed(Self.describe(error))
        }
        return signature
    }

    static func describe(_ error: Unmanaged<CFError>?) -> String {
        guard let error else { return "unknown" }
        return String(describing: error.takeRetainedValue())
    }
}

/// Creates / loads / deletes the device's P-256 key.
///
/// Production generation is **Secure-Enclave, non-exportable, permanent** so the
/// private key never leaves hardware and survives relaunch as a keychain
/// resident. `generateSoftware` is the documented **non-device fallback** (the SE
/// is unavailable on the Simulator and — per plan §3.3 — desktop is explicitly
/// downgraded to a best-effort OS-keychain key); it is the SAME `SecKey` API
/// minus the SE token, so the CSR/signature code path is unchanged.
public enum SecureEnclaveKeyFactory {
    private static let log = Logger(subsystem: "com.yaojia.webterm", category: "se-key")

    /// Generate a NON-EXPORTABLE P-256 key inside the Secure Enclave, marked
    /// permanent + tagged so the keychain can later bind the enrolled leaf to it.
    /// Throws `.secureEnclaveUnavailable` on Simulator / missing entitlement so
    /// callers can fall back to `generateSoftware` for non-device builds.
    public static func generateSecureEnclave(tag: Data) throws -> SecureEnclaveKey {
        try generate(tag: tag, inSecureEnclave: true, permanent: true)
    }

    /// Software P-256 key (NO Secure Enclave token). Simulator / desktop
    /// best-effort / unit tests. `permanent == false` keeps it in-process only.
    public static func generateSoftware(tag: Data? = nil, permanent: Bool = false) throws
        -> SecureEnclaveKey {
        try generate(tag: tag, inSecureEnclave: false, permanent: permanent)
    }

    private static func generate(
        tag: Data?, inSecureEnclave: Bool, permanent: Bool
    ) throws -> SecureEnclaveKey {
        var privateKeyAttrs: [String: Any] = [kSecAttrIsPermanent as String: permanent]
        if let tag {
            privateKeyAttrs[kSecAttrApplicationTag as String] = tag
        }

        if inSecureEnclave {
            var accessError: Unmanaged<CFError>?
            guard
                let access = SecAccessControlCreateWithFlags(
                    kCFAllocatorDefault,
                    kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
                    .privateKeyUsage,
                    &accessError
                )
            else {
                throw SecureEnclaveKeyError.keyGenerationFailed(
                    "access control: \(SecureEnclaveKey.describe(accessError))"
                )
            }
            privateKeyAttrs[kSecAttrAccessControl as String] = access
        }

        var attributes: [String: Any] = [
            kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
            kSecAttrKeySizeInBits as String: 256,
            kSecPrivateKeyAttrs as String: privateKeyAttrs,
        ]
        if inSecureEnclave {
            attributes[kSecAttrTokenID as String] = kSecAttrTokenIDSecureEnclave
        }

        var error: Unmanaged<CFError>?
        guard let key = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
            let description = SecureEnclaveKey.describe(error)
            if inSecureEnclave {
                log.error(
                    "Secure Enclave keygen failed (simulator / no entitlement?): \(description, privacy: .public)"
                )
                throw SecureEnclaveKeyError.secureEnclaveUnavailable(description)
            }
            throw SecureEnclaveKeyError.keyGenerationFailed(description)
        }
        return SecureEnclaveKey(privateKey: key)
    }

    /// Load a previously-generated key (SE or software) by its keychain tag.
    /// `nil` if none exists (the normal pre-enroll state).
    public static func load(tag: Data) throws -> SecureEnclaveKey? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassKey,
            kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
            kSecAttrApplicationTag as String: tag,
            kSecReturnRef as String: true,
        ]
        var result: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &result)
        if status == errSecItemNotFound { return nil }
        guard status == errSecSuccess, let value = result else {
            throw SecureEnclaveKeyError.keychain(status)
        }
        // Safe: the query pins kSecClassKey, so a match is always a SecKey.
        let key = value as! SecKey
        return SecureEnclaveKey(privateKey: key)
    }

    /// Delete the device key by tag. Idempotent.
    public static func delete(tag: Data) throws {
        let query: [String: Any] = [
            kSecClass as String: kSecClassKey,
            kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
            kSecAttrApplicationTag as String: tag,
        ]
        let status = SecItemDelete(query as CFDictionary)
        guard status == errSecSuccess || status == errSecItemNotFound else {
            throw SecureEnclaveKeyError.keychain(status)
        }
    }
}
