import Foundation
import Security
import CryptoKit

/// Secure Enclave key manager providing P-256 key generation, signing, and management.
/// All public keys are returned in uncompressed SEC1 format (65 bytes: 04 || x || y).
/// All binary data uses base64url encoding (RFC 4648 §5, no padding).
@objc public class SecureEnclaveKeyManager: NSObject {

  @objc public func isAvailable() -> Bool {
    if #available(iOS 13.0, *) {
      return SecureEnclave.isAvailable
    }
    return false
  }

  @objc public func hasKey(_ tag: String) -> Bool {
    let query = baseQuery(for: tag, returning: true)
    var item: CFTypeRef?
    let status = SecItemCopyMatching(query as CFDictionary, &item)
    return status == errSecSuccess
  }

  @objc public func generateKeyPair(_ tag: String) throws -> String {
    let tagData = tag.data(using: .utf8)!

    let access = SecAccessControlCreateWithFlags(
      kCFAllocatorDefault,
      kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
      .privateKeyUsage,
      nil
    )!

    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: tagData,
        kSecAttrAccessControl as String: access,
      ] as [String: Any],
    ]

    var error: Unmanaged<CFError>?
    guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
      throw NSError(
        domain: "SecureEnclaveKeyManager",
        code: 1,
        userInfo: [NSLocalizedDescriptionKey: "\(error!.takeRetainedValue())"]
      )
    }

    guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
      throw NSError(
        domain: "SecureEnclaveKeyManager",
        code: 2,
        userInfo: [NSLocalizedDescriptionKey: "Failed to extract public key"]
      )
    }

    let publicKeyData = try exportPublicKey(publicKey)
    return base64urlEncode(publicKeyData)
  }

  /// Returns the base64url-encoded public key, or an empty string if the key does not exist.
  /// The ObjC++ bridge maps empty string to null.
  @objc public func getPublicKey(_ tag: String) throws -> String {
    let query = baseQuery(for: tag, returning: true)
    var item: CFTypeRef?
    let status = SecItemCopyMatching(query as CFDictionary, &item)

    guard status == errSecSuccess, let ref = item else {
      // Key not found — return empty string so the bridge can resolve null
      return ""
    }

    let privateKey = ref as! SecKey
    guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
      throw NSError(
        domain: "SecureEnclaveKeyManager",
        code: 2,
        userInfo: [NSLocalizedDescriptionKey: "Failed to extract public key"]
      )
    }

    let publicKeyData = try exportPublicKey(publicKey)
    return base64urlEncode(publicKeyData)
  }

  @objc public func sign(_ tag: String, payload: String) throws -> String {
    let query = baseQuery(for: tag, returning: true)
    var item: CFTypeRef?
    let status = SecItemCopyMatching(query as CFDictionary, &item)

    guard status == errSecSuccess, let ref = item else {
      throw NSError(
        domain: "SecureEnclaveKeyManager",
        code: 3,
        userInfo: [NSLocalizedDescriptionKey: "Key not found for tag: \(tag)"]
      )
    }

    let privateKey = ref as! SecKey

    guard let payloadData = base64urlDecode(payload) else {
      throw NSError(
        domain: "SecureEnclaveKeyManager",
        code: 4,
        userInfo: [NSLocalizedDescriptionKey: "Invalid base64url payload"]
      )
    }

    var error: Unmanaged<CFError>?
    guard let signature = SecKeyCreateSignature(
      privateKey,
      .ecdsaSignatureMessageX962SHA256,
      payloadData as CFData,
      &error
    ) else {
      throw NSError(
        domain: "SecureEnclaveKeyManager",
        code: 5,
        userInfo: [NSLocalizedDescriptionKey: "\(error!.takeRetainedValue())"]
      )
    }

    return base64urlEncode(signature as Data)
  }

  @objc public func deleteKey(_ tag: String) {
    let query = baseQuery(for: tag, returning: false)
    SecItemDelete(query as CFDictionary)
  }

  // MARK: - Private helpers

  private func baseQuery(for tag: String, returning returnRef: Bool) -> [String: Any] {
    var query: [String: Any] = [
      kSecClass as String: kSecClassKey,
      kSecAttrApplicationTag as String: tag.data(using: .utf8)!,
      kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
    ]
    if returnRef {
      query[kSecReturnRef as String] = true
    }
    return query
  }

  private func exportPublicKey(_ publicKey: SecKey) throws -> Data {
    var error: Unmanaged<CFError>?
    guard let data = SecKeyCopyExternalRepresentation(publicKey, &error) else {
      throw NSError(
        domain: "SecureEnclaveKeyManager",
        code: 2,
        userInfo: [NSLocalizedDescriptionKey: "\(error!.takeRetainedValue())"]
      )
    }
    // SecKeyCopyExternalRepresentation returns uncompressed SEC1 (04 || x || y)
    return data as Data
  }

  private func base64urlEncode(_ data: Data) -> String {
    data.base64EncodedString()
      .replacingOccurrences(of: "+", with: "-")
      .replacingOccurrences(of: "/", with: "_")
      .replacingOccurrences(of: "=", with: "")
  }

  private func base64urlDecode(_ string: String) -> Data? {
    var base64 = string
      .replacingOccurrences(of: "-", with: "+")
      .replacingOccurrences(of: "_", with: "/")
    let remainder = base64.count % 4
    if remainder > 0 {
      base64 += String(repeating: "=", count: 4 - remainder)
    }
    return Data(base64Encoded: base64)
  }
}
