package wang.yaojia.webterm.api.enroll

import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.security.KeyPairGenerator
import java.security.Signature
import java.security.interfaces.ECPublicKey
import java.security.spec.ECGenParameterSpec

/**
 * B4 · Proves the manual PKCS#10 encoder produces a well-formed, self-signed P-256 CSR that the
 * control-plane `verifyCsrPoPEc` (id-ecPublicKey + prime256v1 SPKI, ecdsa-with-SHA256
 * self-signature) accepts. Runs headless with a SOFTWARE P-256 key via the SAME
 * `Signature("SHA256withECDSA")` path the on-device AndroidKeyStore key uses — so the signing path
 * is byte-identical. Real StrongBox keygen is device-only (`:client-tls-android`).
 */
class CertificateSigningRequestTest {
    /** Software P-256 signer via the SAME JCA `SHA256withECDSA` path used on-device (no StrongBox). */
    private class SoftwareEcSigner : CsrSigner {
        val keyPair = KeyPairGenerator.getInstance("EC").apply {
            initialize(ECGenParameterSpec("secp256r1"))
        }.generateKeyPair()

        override fun publicKeyX963(): ByteArray = EcPointEncoding.x963(keyPair.public as ECPublicKey)

        override fun sign(message: ByteArray): ByteArray =
            Signature.getInstance("SHA256withECDSA").apply {
                initSign(keyPair.private)
                update(message)
            }.sign()
    }

    @Test
    fun csrIsCanonicalPkcs10SequenceOfExactlyThreeElements() {
        val signer = SoftwareEcSigner()

        val der = CertificateSigningRequest.der("web-terminal-device", signer)

        val outer = TestDer.read(der, 0)!!
        assertEquals(0x30, outer.tag, "outer CertificationRequest is a SEQUENCE")
        assertEquals(der.size, outer.end, "no trailing garbage after the CSR")
        val parts = TestDer.children(der, outer)
        assertEquals(3, parts.size)
        assertEquals(0x30, parts[0].tag) // certificationRequestInfo
        assertEquals(0x30, parts[1].tag) // signatureAlgorithm
        assertEquals(0x03, parts[2].tag) // signature BIT STRING
    }

    @Test
    fun csrSelfSignatureVerifiesAgainstTheEmbeddedP256Key() {
        val signer = SoftwareEcSigner()

        val der = CertificateSigningRequest.der("web-terminal-device", signer)

        // Extract the exact CertificationRequestInfo bytes that were signed and the ECDSA signature
        // (the same crypto check verifyCsrPoPEc's req.verify() runs).
        val outer = TestDer.read(der, 0)!!
        val parts = TestDer.children(der, outer)
        val infoBytes = der.copyOfRange(parts[0].start, parts[0].end)
        val bitString = parts[2] // BIT STRING: first content byte is unused-bits (0x00)
        val signature = der.copyOfRange(bitString.valueStart + 1, bitString.valueEnd)

        val ok = Signature.getInstance("SHA256withECDSA").apply {
            initVerify(signer.keyPair.public)
            update(infoBytes)
        }.verify(signature)
        assertTrue(ok, "the CSR self-signature must verify against its own SubjectPublicKeyInfo")
    }

    @Test
    fun csrEmbedsAP256SubjectPublicKeyInfoTheServerVerifierAccepts() {
        val signer = SoftwareEcSigner()
        val point = signer.publicKeyX963()

        val der = CertificateSigningRequest.der("web-terminal-device", signer)

        val outer = TestDer.read(der, 0)!!
        val info = TestDer.children(der, outer)[0]
        val infoChildren = TestDer.children(der, info)
        assertEquals(4, infoChildren.size)
        // version v1(0)
        assertArrayEquals(byteArrayOf(0x02, 0x01, 0x00), der.copyOfRange(infoChildren[0].start, infoChildren[0].end))
        assertEquals(0xA0, infoChildren[3].tag) // [0] IMPLICIT attributes
        assertEquals(0, infoChildren[3].valueEnd - infoChildren[3].valueStart) // empty SET

        // subjectPublicKeyInfo ::= SEQUENCE { AlgorithmIdentifier, BIT STRING point }
        val spki = infoChildren[2]
        val spkiChildren = TestDer.children(der, spki)
        assertEquals(2, spkiChildren.size)
        val algIdChildren = TestDer.children(der, spkiChildren[0])
        // AlgorithmIdentifier { id-ecPublicKey, prime256v1 } — the exact OIDs verifyCsrPoPEc pins.
        assertArrayEquals(
            byteArrayOf(0x06, 0x07, 0x2A, 0x86.toByte(), 0x48, 0xCE.toByte(), 0x3D, 0x02, 0x01),
            der.copyOfRange(algIdChildren[0].start, algIdChildren[0].end),
        )
        assertArrayEquals(
            byteArrayOf(0x06, 0x08, 0x2A, 0x86.toByte(), 0x48, 0xCE.toByte(), 0x3D, 0x03, 0x01, 0x07),
            der.copyOfRange(algIdChildren[1].start, algIdChildren[1].end),
        )
        // BIT STRING content = 0x00 unused-bits + the exact 65-byte point.
        val bitString = spkiChildren[1]
        assertEquals(0x03, bitString.tag)
        assertEquals(0x00, der[bitString.valueStart].toInt() and 0xFF)
        assertArrayEquals(point, der.copyOfRange(bitString.valueStart + 1, bitString.valueEnd))
    }

    @Test
    fun signatureAlgorithmIsEcdsaWithSha256() {
        val signer = SoftwareEcSigner()
        val der = CertificateSigningRequest.der("web-terminal-device", signer)
        val outer = TestDer.read(der, 0)!!
        val algId = TestDer.children(der, outer)[1]
        val oid = TestDer.children(der, algId)[0]
        assertArrayEquals(
            byteArrayOf(0x06, 0x08, 0x2A, 0x86.toByte(), 0x48, 0xCE.toByte(), 0x3D, 0x04, 0x03, 0x02),
            der.copyOfRange(oid.start, oid.end),
        )
    }

    @Test
    fun subjectCommonNameIsEncodedAsUtf8String() {
        val signer = SoftwareEcSigner()
        val der = CertificateSigningRequest.der("my-pixel", signer)
        val outer = TestDer.read(der, 0)!!
        val info = TestDer.children(der, outer)[0]
        val name = TestDer.children(der, info)[1] // subject Name
        val rdn = TestDer.children(der, name)[0] // SET
        val attr = TestDer.children(der, rdn)[0] // SEQUENCE { OID, value }
        val attrChildren = TestDer.children(der, attr)
        // OID 2.5.4.3 (commonName), then a UTF8String (tag 0x0C) carrying the CN bytes.
        assertArrayEquals(byteArrayOf(0x06, 0x03, 0x55, 0x04, 0x03), der.copyOfRange(attrChildren[0].start, attrChildren[0].end))
        assertEquals(0x0C, attrChildren[1].tag)
        assertArrayEquals("my-pixel".toByteArray(), der.copyOfRange(attrChildren[1].valueStart, attrChildren[1].valueEnd))
    }

    @Test
    fun emptySubjectCommonNameIsRejected() {
        val signer = SoftwareEcSigner()
        assertThrows(CsrException.InvalidSubject::class.java) {
            CertificateSigningRequest.der("", signer)
        }
    }

    @Test
    fun aNon65BytePublicKeyIsRejected() {
        val badSigner = object : CsrSigner {
            override fun publicKeyX963(): ByteArray = ByteArray(64) { 0x04 } // wrong length
            override fun sign(message: ByteArray): ByteArray = ByteArray(0)
        }
        assertThrows(CsrException.InvalidPublicKey::class.java) {
            CertificateSigningRequest.der("d", badSigner)
        }
    }

    @Test
    fun aPublicKeyWithoutTheUncompressedPrefixIsRejected() {
        val badSigner = object : CsrSigner {
            override fun publicKeyX963(): ByteArray = ByteArray(65) { 0x02 } // right length, wrong prefix
            override fun sign(message: ByteArray): ByteArray = ByteArray(0)
        }
        assertThrows(CsrException.InvalidPublicKey::class.java) {
            CertificateSigningRequest.der("d", badSigner)
        }
    }
}

/**
 * A throwaway canonical-DER reader for structural assertions (the enroll path itself does no DER
 * parsing — the server verifies; this mirrors the iOS `TestDER` test helper).
 */
internal object TestDer {
    data class Element(val tag: Int, val start: Int, val valueStart: Int, val valueEnd: Int) {
        val end: Int get() = valueEnd
    }

    fun read(bytes: ByteArray, start: Int): Element? {
        if (start < 0 || start + 1 >= bytes.size) return null
        val tag = bytes[start].toInt() and 0xFF
        var index = start + 1
        val first = bytes[index].toInt() and 0xFF
        index += 1
        var length = 0
        if (first and 0x80 == 0) {
            length = first
        } else {
            val count = first and 0x7F
            if (count == 0 || count > 4 || index + count > bytes.size) return null
            repeat(count) {
                length = (length shl 8) or (bytes[index].toInt() and 0xFF)
                index += 1
            }
        }
        val valueEnd = index + length
        if (valueEnd > bytes.size) return null
        return Element(tag = tag, start = start, valueStart = index, valueEnd = valueEnd)
    }

    fun children(bytes: ByteArray, parent: Element): List<Element> {
        val elements = mutableListOf<Element>()
        var index = parent.valueStart
        while (index < parent.valueEnd) {
            val element = read(bytes, index) ?: break
            elements.add(element)
            index = element.valueEnd
        }
        return elements
    }
}
