import Foundation
import Security

/// Failure modes of a PKCS#12 import, each mapped to actionable install-UX copy
/// (C-iOS-3). The three plan-named `OSStatus` mappings are honored:
/// `errSecAuthFailed → .wrongPassphrase`, `errSecDecode → .corruptFile`, and any
/// other non-success status (unsupported/unknown format — the plan's "errSecPkg"
/// bucket) → `.unsupported`.
public enum PKCS12ImportError: Error, Equatable, Sendable {
    /// Wrong passphrase (`errSecAuthFailed`).
    case wrongPassphrase
    /// Not a decodable PKCS#12 blob (`errSecDecode`) — truncated / not a `.p12`.
    case corruptFile
    /// Decoded, but the format/algorithms aren't importable on this OS
    /// (any other non-success `OSStatus`). Retains the raw status for logs.
    case unsupported(OSStatus)
    /// Import succeeded but carried no `SecIdentity` (e.g. a certs-only `.p12`).
    case noIdentity

    public static func == (lhs: PKCS12ImportError, rhs: PKCS12ImportError) -> Bool {
        switch (lhs, rhs) {
        case (.wrongPassphrase, .wrongPassphrase),
             (.corruptFile, .corruptFile),
             (.noIdentity, .noIdentity):
            return true
        case let (.unsupported(a), .unsupported(b)):
            return a == b
        default:
            return false
        }
    }
}

/// Imports a `.p12` into a `ClientIdentity` via `SecPKCS12Import`.
///
/// Memory-only: `SecPKCS12Import` returns the identity/chain as CoreFoundation
/// handles without writing to the keychain, so callers control persistence
/// (see `KeychainClientIdentityStore`).
public enum PKCS12Importer {
    public static func importIdentity(
        data: Data, passphrase: String
    ) throws -> ClientIdentity {
        let options = [kSecImportExportPassphrase as String: passphrase] as CFDictionary
        var rawItems: CFArray?
        let status = SecPKCS12Import(data as CFData, options, &rawItems)

        switch status {
        case errSecSuccess:
            break
        case errSecAuthFailed:
            throw PKCS12ImportError.wrongPassphrase
        case errSecDecode:
            throw PKCS12ImportError.corruptFile
        default:
            // Covers unsupported formats/algorithms and any other failure —
            // the plan's `errSecPkg → .unsupported` mapping (that symbol does
            // not exist in the SDK; the status is preserved for diagnostics).
            throw PKCS12ImportError.unsupported(status)
        }

        guard let items = rawItems as? [[String: Any]], let first = items.first,
              let identityValue = first[kSecImportItemIdentity as String]
        else {
            throw PKCS12ImportError.noIdentity
        }
        // Force-cast is safe: `kSecImportItemIdentity` is always a SecIdentity.
        let secIdentity = identityValue as! SecIdentity
        let chain = (first[kSecImportItemCertChain as String] as? [SecCertificate]) ?? []
        return ClientIdentity(
            secIdentity: secIdentity,
            issuerCertificates: issuerChain(from: chain, identity: secIdentity)
        )
    }

    /// The chain from `SecPKCS12Import` includes the leaf at index 0; the
    /// `URLCredential` must carry only the *issuer* certs (Apple: do not repeat
    /// the identity's own cert). Drops whichever chain entry DER-matches the
    /// identity's leaf.
    private static func issuerChain(
        from chain: [SecCertificate], identity: SecIdentity
    ) -> [SecCertificate] {
        var leaf: SecCertificate?
        SecIdentityCopyCertificate(identity, &leaf)
        guard let leafData = leaf.map({ SecCertificateCopyData($0) as Data }) else {
            return chain
        }
        return chain.filter { SecCertificateCopyData($0) as Data != leafData }
    }
}
