import Foundation
import X509

@_spi(CMS) import X509

/// CMS signing for the enrollment `.mobileconfig`.
///
/// A `.mobileconfig` is "signed" by replacing the plain XML plist with a CMS `SignedData` blob that
/// *encapsulates* that plist (an attached signature, not a detached one — the profile bytes travel
/// inside the signature). System Settings then shows the profile as verified and attributes it to the
/// signing certificate's subject, instead of "Not Signed".
///
/// Why bother, when an unsigned profile installs too: on modern macOS, certificate payloads inside an
/// *unsigned* profile are installed but not granted trust. Since this profile's whole job is to deliver
/// a CA root plus a PKCS#12 identity that the MDM check-in TLS then depends on, leaving it unsigned
/// makes the trust story depend entirely on the separate `security add-trusted-cert` step. Signing with
/// a CA-issued leaf — a CA the guest already trusts by the time it opens the profile — removes that
/// variable. Signing is deliberately best-effort at the call site: a signing failure must degrade to
/// the previous unsigned behavior rather than break enrollment outright.
public enum MDMProfileSigner {
    public enum SigningError: Error, CustomStringConvertible {
        case cms(String)
        public var description: String {
            if case .cms(let s) = self { return "profile CMS signing failed: \(s)" }
            return ""
        }
    }

    /// Wrap `profile` (an XML-plist `.mobileconfig`) in an attached CMS `SignedData` signed by a leaf
    /// issued from `ca`. Returns the DER bytes that become the staged `.mobileconfig`.
    public static func sign(profile: Data, using ca: MDMCertificateAuthority) throws -> Data {
        let signer = try ca.issueProfileSigningCertificate()
        do {
            let der = try CMS.sign(
                profile,
                signatureAlgorithm: .sha256WithRSAEncryption,
                certificate: signer.certificate,
                privateKey: Certificate.PrivateKey(signer.privateKey),
                detached: false)
            return Data(der)
        } catch {
            throw SigningError.cms("\(error)")
        }
    }
}
