import Crypto
import Foundation
import SwiftASN1
import X509

/// The admin-domain trust settings file, `/Library/Security/Trust Settings/Admin.plist`, built
/// host-side so a guest can be given a trusted root **without** calling Security.framework.
///
/// Why this exists at all: on macOS 27 there is no supported non-interactive way to trust a local CA.
///  * `security add-trusted-cert -d` needs the `com.apple.trust-settings.admin` right, whose rule is
///    `k-of-n 1` over `[entitled, authenticate-admin]`. `security` is not entitled, so it falls to
///    admin authentication — a SecurityAgent sheet owned by a root CLI with no GUI app, which never
///    becomes key and cannot be driven.
///  * Relaxing that right with `security authorizationdb write … allow` is the recipe every CI
///    write-up gives. **It silently no-ops on macOS 27**: a live run read the rule straight back and
///    got the original `[entitled, authenticate-admin]` unchanged.
///  * A `com.apple.security.root` payload in a *manually installed* configuration profile has not
///    been auto-trusted for TLS since macOS Ventura (auto-trust survives only for roots delivered by
///    an MDM — the thing enrollment is trying to reach).
///
/// What is left is the file the API would have written. `/Library/Security/Trust Settings` is
/// `root:wheel` and carries **no SIP `restricted` flag**, so root can write it with SIP enabled;
/// `trustd` reads the trust domain from there on its next start. That sidesteps the authorization
/// check entirely, because no authorization-gated API is called.
///
/// This encoder is deliberately conservative: it writes the same shape `add-trusted-cert -r trustRoot`
/// produces — one entry per certificate, keyed by SHA-1, with a single blanket trust result and no
/// per-policy scoping. Nothing here is guesswork about *semantics*; the only inference is the
/// serialization, and the guest verifies the outcome with its own `security verify-cert -p ssl`
/// rather than trusting that this file was accepted.
public enum MDMAdminTrustSettings {
    /// `kSecTrustSettingsResultTrustRoot` — "this certificate is a trusted anchor". The value the
    /// Security framework itself stores for `add-trusted-cert -r trustRoot`.
    static let trustRootResult = 1
    /// The schema version Security writes into this file.
    static let trustVersion = 1

    /// Build the Admin.plist bytes granting `certificate` blanket root trust.
    ///
    /// - Parameter now: the entry's `modDate`. Injectable so the encoding is deterministic in tests.
    public static func plist(trusting certificate: Certificate, now: Date = Date()) throws -> Data {
        var issuerSerializer = DER.Serializer()
        try issuerSerializer.serialize(certificate.issuer)

        var certSerializer = DER.Serializer()
        try certSerializer.serialize(certificate)
        let certificateDER = Data(certSerializer.serializedBytes)

        // Security keys the trust list by the certificate's SHA-1 fingerprint, upper-case hex. SHA-1
        // is not a security property here — it is a lookup key in a file only root can write, and the
        // format is Apple's, not ours.
        let fingerprint = Insecure.SHA1.hash(data: certificateDER)
            .map { String(format: "%02X", $0) }.joined()

        let entry: [String: Any] = [
            "issuerName": Data(issuerSerializer.serializedBytes),
            "modDate": now,
            "serialNumber": Data(certificate.serialNumber.bytes),
            // One blanket setting: trust this as a root for every policy. `add-trusted-cert` with no
            // `-p` writes exactly this — a lone result with no `kSecTrustSettingsPolicy` scoping it.
            "trustSettings": [["kSecTrustSettingsResult": trustRootResult]],
        ]
        let root: [String: Any] = [
            "trustVersion": trustVersion,
            "trustList": [fingerprint: entry],
        ]
        return try PropertyListSerialization.data(
            fromPropertyList: root, format: .xml, options: 0)
    }
}
