import Foundation

/// C-iOS · Manual DER encoder for a P-256 PKCS#10 `CertificationRequest`.
///
/// Built by hand (no CryptoKit / SecCertificate helpers) so the exact bytes are
/// under our control and the request is signed with a Secure-Enclave `SecKey`
/// via `SecKeyCreateSignature(.ecdsaSignatureMessageX962SHA256)` — see the
/// `[FIX C-native, ClientTLS trap]` note on `SecureEnclaveKey`.
///
/// The output must satisfy the control-plane `verifyCsrPoPEc` (A1): an EC P-256
/// `SubjectPublicKeyInfo` (algorithm `id-ecPublicKey` + namedCurve `prime256v1`),
/// a self-signature under `ecdsa-with-SHA256`, and a valid PoP. Encoding is
/// strictly canonical DER (minimal lengths) so the server's re-serialization of
/// `CertificationRequestInfo` matches the bytes we signed.
///
/// ```
/// CertificationRequest ::= SEQUENCE {
///     certificationRequestInfo CertificationRequestInfo,
///     signatureAlgorithm       AlgorithmIdentifier,   -- ecdsa-with-SHA256
///     signature                BIT STRING }           -- X9.62 DER ECDSA-Sig
///
/// CertificationRequestInfo ::= SEQUENCE {
///     version       INTEGER { v1(0) },
///     subject       Name,
///     subjectPKInfo SubjectPublicKeyInfo,
///     attributes    [0] IMPLICIT SET OF Attribute }    -- empty
/// ```
public enum CertificateSigningRequest {
    public enum CSRError: Error, Equatable, Sendable {
        /// The public key was not the expected 65-byte X9.63 uncompressed point.
        case invalidPublicKey
        /// The empty subject CN is not encodable.
        case invalidSubject
    }

    /// P-256 uncompressed public point is `0x04 || X(32) || Y(32)` = 65 bytes.
    private static let uncompressedP256PointLength = 65

    /// Build and self-sign a P-256 PKCS#10 CSR DER for `signer`'s key.
    ///
    /// - Parameters:
    ///   - subjectCommonName: the CSR subject CN. The device leaf's identity is
    ///     driven server-side by the ownership-verified subdomain SAN, so this is
    ///     descriptive only; it must be non-empty.
    ///   - signer: the P-256 hardware key that provides the public key and signs
    ///     the `CertificationRequestInfo`.
    public static func der(
        subjectCommonName: String, signer: any P256HardwareKey
    ) throws -> Data {
        guard !subjectCommonName.isEmpty else { throw CSRError.invalidSubject }

        let publicPoint = [UInt8](try signer.publicKeyX963())
        guard publicPoint.count == uncompressedP256PointLength, publicPoint[0] == 0x04 else {
            throw CSRError.invalidPublicKey
        }

        let requestInfo = certificationRequestInfo(
            subjectCommonName: subjectCommonName, publicPoint: publicPoint
        )
        let signature = [UInt8](try signer.sign(Data(requestInfo)))

        let request = DERWriter.sequence([
            requestInfo,
            ecdsaWithSHA256AlgorithmIdentifier,
            DERWriter.bitString(signature),
        ])
        return Data(request)
    }

    // MARK: - CertificationRequestInfo

    private static func certificationRequestInfo(
        subjectCommonName: String, publicPoint: [UInt8]
    ) -> [UInt8] {
        DERWriter.sequence([
            DERWriter.integer0, // version v1(0)
            name(commonName: subjectCommonName),
            subjectPublicKeyInfo(publicPoint: publicPoint),
            DERWriter.emptyAttributesContext0, // [0] IMPLICIT SET OF Attribute (empty)
        ])
    }

    /// `Name ::= SEQUENCE OF RelativeDistinguishedName` with a single CN RDN.
    private static func name(commonName: String) -> [UInt8] {
        let attribute = DERWriter.sequence([
            DERWriter.oid(OID.commonName),
            DERWriter.utf8String(commonName),
        ])
        let rdn = DERWriter.set([attribute])
        return DERWriter.sequence([rdn])
    }

    /// `SubjectPublicKeyInfo` for an EC P-256 key: `id-ecPublicKey` +
    /// `prime256v1` named curve, then the uncompressed point as a BIT STRING.
    private static func subjectPublicKeyInfo(publicPoint: [UInt8]) -> [UInt8] {
        let algorithm = DERWriter.sequence([
            DERWriter.oid(OID.ecPublicKey),
            DERWriter.oid(OID.prime256v1),
        ])
        return DERWriter.sequence([
            algorithm,
            DERWriter.bitString(publicPoint),
        ])
    }

    /// `AlgorithmIdentifier` for `ecdsa-with-SHA256` — no parameters (absent, per
    /// RFC 5758), which is exactly what the server's verifier expects.
    private static let ecdsaWithSHA256AlgorithmIdentifier: [UInt8] =
        DERWriter.sequence([DERWriter.oid(OID.ecdsaWithSHA256)])
}

// MARK: - Object identifiers (DER content bytes, tag/length added by DERWriter.oid)

private enum OID {
    /// 1.2.840.10045.2.1 — id-ecPublicKey.
    static let ecPublicKey: [UInt8] = [0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01]
    /// 1.2.840.10045.3.1.7 — prime256v1 / secp256r1.
    static let prime256v1: [UInt8] = [0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]
    /// 1.2.840.10045.4.3.2 — ecdsa-with-SHA256.
    static let ecdsaWithSHA256: [UInt8] = [0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02]
    /// 2.5.4.3 — id-at-commonName.
    static let commonName: [UInt8] = [0x55, 0x04, 0x03]
}

// MARK: - Minimal canonical DER writer

/// A tiny DER encoder. Every helper returns a fully-formed TLV so callers just
/// concatenate children — canonical minimal-length encoding throughout.
enum DERWriter {
    private static let tagInteger: UInt8 = 0x02
    private static let tagBitString: UInt8 = 0x03
    private static let tagOID: UInt8 = 0x06
    private static let tagUTF8String: UInt8 = 0x0C
    private static let tagSequence: UInt8 = 0x30
    private static let tagSet: UInt8 = 0x31
    private static let tagContext0Constructed: UInt8 = 0xA0

    /// `INTEGER 0` — the fixed PKCS#10 version v1(0).
    static let integer0: [UInt8] = [tagInteger, 0x01, 0x00]

    /// `[0] IMPLICIT SET OF Attribute`, empty — `A0 00`.
    static let emptyAttributesContext0: [UInt8] = [tagContext0Constructed, 0x00]

    static func sequence(_ children: [[UInt8]]) -> [UInt8] {
        tlv(tagSequence, children.flatMap { $0 })
    }

    static func set(_ children: [[UInt8]]) -> [UInt8] {
        tlv(tagSet, children.flatMap { $0 })
    }

    static func oid(_ content: [UInt8]) -> [UInt8] {
        tlv(tagOID, content)
    }

    static func utf8String(_ value: String) -> [UInt8] {
        tlv(tagUTF8String, [UInt8](Data(value.utf8)))
    }

    /// BIT STRING with zero unused bits (all our bit strings are byte-aligned).
    static func bitString(_ content: [UInt8]) -> [UInt8] {
        tlv(tagBitString, [0x00] + content)
    }

    /// Tag-Length-Value with canonical DER length encoding.
    private static func tlv(_ tag: UInt8, _ value: [UInt8]) -> [UInt8] {
        [tag] + length(value.count) + value
    }

    /// DER length: short form (<128) or long form (0x80 | byteCount, big-endian).
    private static func length(_ count: Int) -> [UInt8] {
        if count < 0x80 { return [UInt8(count)] }
        var value = count
        var bytes: [UInt8] = []
        while value > 0 {
            bytes.insert(UInt8(value & 0xFF), at: 0)
            value >>= 8
        }
        return [0x80 | UInt8(bytes.count)] + bytes
    }
}
