package wang.yaojia.webterm.api.enroll

/**
 * B4 · Manual, canonical-DER encoder for a P-256 PKCS#10 `CertificationRequest` — a byte-for-byte
 * port of the iOS `ClientTLS.CertificateSigningRequest`.
 *
 * Built by hand (no JCA CSR helper) so the exact bytes are under our control and the request is
 * signed by the [CsrSigner] (an AndroidKeyStore hardware key in production, a software P-256 key in
 * tests) via `SHA256withECDSA`. The output must satisfy the control-plane `verifyCsrPoPEc`: an EC
 * P-256 `SubjectPublicKeyInfo` (`id-ecPublicKey` + `prime256v1`), an `ecdsa-with-SHA256`
 * self-signature, 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 object CertificateSigningRequest {
    /** P-256 uncompressed public point is `0x04 || X(32) || Y(32)` = 65 bytes. */
    private const val UNCOMPRESSED_P256_POINT_LENGTH = 65

    /**
     * Build and self-sign a P-256 PKCS#10 CSR DER for [signer]'s key.
     *
     * @param 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.
     * @param signer the P-256 hardware key that provides the public key and signs the
     *   `CertificationRequestInfo`.
     * @throws CsrException.InvalidSubject on an empty CN; [CsrException.InvalidPublicKey] if the
     *   signer's public key is not a 65-byte X9.63 P-256 point.
     */
    public fun der(subjectCommonName: String, signer: CsrSigner): ByteArray {
        if (subjectCommonName.isEmpty()) throw CsrException.InvalidSubject

        val publicPoint = signer.publicKeyX963()
        if (publicPoint.size != UNCOMPRESSED_P256_POINT_LENGTH || publicPoint[0].toInt() != 0x04) {
            throw CsrException.InvalidPublicKey
        }

        val requestInfo = certificationRequestInfo(subjectCommonName, publicPoint)
        val signature = signer.sign(requestInfo)

        return DerWriter.sequence(
            listOf(
                requestInfo,
                ECDSA_WITH_SHA256_ALGORITHM_IDENTIFIER,
                DerWriter.bitString(signature),
            ),
        )
    }

    // ── CertificationRequestInfo ────────────────────────────────────────────────────────────

    private fun certificationRequestInfo(subjectCommonName: String, publicPoint: ByteArray): ByteArray =
        DerWriter.sequence(
            listOf(
                DerWriter.INTEGER_0, // version v1(0)
                name(subjectCommonName),
                subjectPublicKeyInfo(publicPoint),
                DerWriter.EMPTY_ATTRIBUTES_CONTEXT0, // [0] IMPLICIT SET OF Attribute (empty)
            ),
        )

    /** `Name ::= SEQUENCE OF RelativeDistinguishedName` with a single CN RDN. */
    private fun name(commonName: String): ByteArray {
        val attribute = DerWriter.sequence(
            listOf(DerWriter.oid(Oid.COMMON_NAME), DerWriter.utf8String(commonName)),
        )
        val rdn = DerWriter.set(listOf(attribute))
        return DerWriter.sequence(listOf(rdn))
    }

    /**
     * `SubjectPublicKeyInfo` for an EC P-256 key: `id-ecPublicKey` + `prime256v1` named curve, then
     * the uncompressed point as a BIT STRING.
     */
    private fun subjectPublicKeyInfo(publicPoint: ByteArray): ByteArray {
        val algorithm = DerWriter.sequence(
            listOf(DerWriter.oid(Oid.EC_PUBLIC_KEY), DerWriter.oid(Oid.PRIME256V1)),
        )
        return DerWriter.sequence(listOf(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 val ECDSA_WITH_SHA256_ALGORITHM_IDENTIFIER: ByteArray =
        DerWriter.sequence(listOf(DerWriter.oid(Oid.ECDSA_WITH_SHA256)))
}

/** Object identifiers (DER content bytes; tag/length added by [DerWriter.oid]). */
private object Oid {
    /** 1.2.840.10045.2.1 — id-ecPublicKey. */
    val EC_PUBLIC_KEY = byteArrayOf(0x2A, 0x86.toByte(), 0x48, 0xCE.toByte(), 0x3D, 0x02, 0x01)

    /** 1.2.840.10045.3.1.7 — prime256v1 / secp256r1. */
    val PRIME256V1 = byteArrayOf(0x2A, 0x86.toByte(), 0x48, 0xCE.toByte(), 0x3D, 0x03, 0x01, 0x07)

    /** 1.2.840.10045.4.3.2 — ecdsa-with-SHA256. */
    val ECDSA_WITH_SHA256 = byteArrayOf(0x2A, 0x86.toByte(), 0x48, 0xCE.toByte(), 0x3D, 0x04, 0x03, 0x02)

    /** 2.5.4.3 — id-at-commonName. */
    val COMMON_NAME = byteArrayOf(0x55, 0x04, 0x03)
}

/**
 * A tiny canonical-DER encoder. Every helper returns a fully-formed TLV so callers just concatenate
 * children — canonical minimal-length encoding throughout. Internal so its byte layout is
 * unit-testable in isolation.
 */
internal object DerWriter {
    private const val TAG_INTEGER: Byte = 0x02
    private const val TAG_BIT_STRING: Byte = 0x03
    private const val TAG_OID: Byte = 0x06
    private const val TAG_UTF8_STRING: Byte = 0x0C
    private const val TAG_SEQUENCE: Byte = 0x30
    private const val TAG_SET: Byte = 0x31
    private const val TAG_CONTEXT0_CONSTRUCTED: Byte = 0xA0.toByte()

    /** `INTEGER 0` — the fixed PKCS#10 version v1(0). */
    val INTEGER_0: ByteArray = byteArrayOf(TAG_INTEGER, 0x01, 0x00)

    /** `[0] IMPLICIT SET OF Attribute`, empty — `A0 00`. */
    val EMPTY_ATTRIBUTES_CONTEXT0: ByteArray = byteArrayOf(TAG_CONTEXT0_CONSTRUCTED, 0x00)

    fun sequence(children: List<ByteArray>): ByteArray = tlv(TAG_SEQUENCE, concat(children))

    fun set(children: List<ByteArray>): ByteArray = tlv(TAG_SET, concat(children))

    fun oid(content: ByteArray): ByteArray = tlv(TAG_OID, content)

    fun utf8String(value: String): ByteArray = tlv(TAG_UTF8_STRING, value.encodeToByteArray())

    /** BIT STRING with zero unused bits (all our bit strings are byte-aligned). */
    fun bitString(content: ByteArray): ByteArray = tlv(TAG_BIT_STRING, byteArrayOf(0x00) + content)

    /** Tag-Length-Value with canonical DER length encoding. */
    private fun tlv(tag: Byte, value: ByteArray): ByteArray = byteArrayOf(tag) + length(value.size) + value

    /** DER length: short form (<128) or long form (`0x80 | byteCount`, big-endian). */
    private fun length(count: Int): ByteArray {
        if (count < 0x80) return byteArrayOf(count.toByte())
        var value = count
        val bytes = ArrayDeque<Byte>()
        while (value > 0) {
            bytes.addFirst((value and 0xFF).toByte())
            value = value ushr 8
        }
        return byteArrayOf((0x80 or bytes.size).toByte()) + bytes.toByteArray()
    }

    private fun concat(chunks: List<ByteArray>): ByteArray {
        val total = chunks.sumOf { it.size }
        val out = ByteArray(total)
        var offset = 0
        for (chunk in chunks) {
            System.arraycopy(chunk, 0, out, offset, chunk.size)
            offset += chunk.size
        }
        return out
    }
}
