import Crypto
import Foundation
import SwiftASN1
import X509
import _CryptoExtras

/// The device identity baked into the enrollment profile (docs/MACOS_VM_MDM.md §4). The host
/// generates the keypair, the CA issues a `clientAuth` leaf, and the pair is exported as a
/// password-protected PKCS#12 for the profile's `com.apple.security.pkcs12` payload. The device then
/// signs every check-in/command request (`Mdm-Signature`) with this identity; the server verifies
/// against the CA.
///
/// This is the seam the deferred SCEP path (§8 spike) would slot behind: swap "host generates + bakes
/// PKCS#12" for "device generates + fetches via /scep" without touching the profile's MDM payload or
/// the server's check-in/command handling.
public struct MDMDeviceIdentity: Sendable {
    /// The issued device certificate (also what the server holds to verify `Mdm-Signature`).
    public let certificate: Certificate
    /// The device private key (host-side; travels only inside the staged profile's PKCS#12).
    public let privateKey: _RSA.Signing.PrivateKey
    /// The random PKCS#12 import password, also written into the profile's `Password` key.
    public let password: String

    private init(
        certificate: Certificate,
        privateKey: _RSA.Signing.PrivateKey,
        password: String
    ) {
        self.certificate = certificate
        self.privateKey = privateKey
        self.password = password
    }

    /// Issue a fresh device identity from the CA.
    public static func issue(
        from ca: MDMCertificateAuthority, deviceName: String = "Nucleic macOS Guest", now: Date = Date()
    ) throws -> MDMDeviceIdentity {
        let key = try _RSA.Signing.PrivateKey(keySize: .bits2048)
        let subject = try DistinguishedName { CommonName(deviceName) }
        let csr = try CertificateSigningRequest(
            version: .v1, subject: subject, privateKey: Certificate.PrivateKey(key),
            attributes: CertificateSigningRequest.Attributes(),
            signatureAlgorithm: .sha256WithRSAEncryption)
        let cert = try ca.issueDeviceCertificate(fromCSR: csr, now: now)
        return MDMDeviceIdentity(
            certificate: cert, privateKey: key, password: randomPassword())
    }

    // MARK: - Persistence (host-side only)

    /// Codable representation retained beside the golden base. The private key and PKCS#12 password
    /// are secrets and must never be copied into a session clone or logged.
    public struct Persisted: Codable, Sendable {
        public var certificatePEM: String
        public var privateKeyPEM: String
        public var password: String

        public init(certificatePEM: String, privateKeyPEM: String, password: String) {
            self.certificatePEM = certificatePEM
            self.privateKeyPEM = privateKeyPEM
            self.password = password
        }
    }

    public func persisted() throws -> Persisted {
        Persisted(
            certificatePEM: try certificate.serializeAsPEM().pemString,
            privateKeyPEM: try Certificate.PrivateKey(privateKey).serializeAsPEM().pemString,
            password: password)
    }

    public init(persisted p: Persisted) throws {
        let certificate = try Certificate(pemEncoded: p.certificatePEM)
        let document = try PEMDocument(pemString: p.privateKeyPEM)
        let privateKey = try _RSA.Signing.PrivateKey(derRepresentation: document.derBytes)
        self.init(certificate: certificate, privateKey: privateKey, password: p.password)
    }

    /// The identity as a PKCS#12 blob for the profile payload.
    public func pkcs12() throws -> Data {
        try PKCS12.export(certificate: certificate, privateKey: privateKey, password: password)
    }

    /// A URL-safe random import password.
    public static func randomPassword() -> String {
        var bytes = [UInt8](repeating: 0, count: 24)
        for i in bytes.indices { bytes[i] = UInt8.random(in: 0...255) }
        return Data(bytes).base64EncodedString()
            .replacingOccurrences(of: "+", with: "-")
            .replacingOccurrences(of: "/", with: "_")
            .replacingOccurrences(of: "=", with: "")
    }
}
