package wang.yaojia.webterm.tlsandroid

import androidx.test.ext.junit.runners.AndroidJUnit4
import java.security.Signature
import org.junit.After
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import wang.yaojia.webterm.api.enroll.CertificateSigningRequest

/**
 * B4 · Instrumented (real AndroidKeyStore — NOT Robolectric, plan §7) proof that the generated
 * device key is hardware-backed, NON-EXPORTABLE, and produces a self-signed P-256 CSR the
 * control-plane accepts. COMPILES in CI here; RUNS on a device/emulator during device QA
 * (StrongBox availability is device-dependent — [HardwareKeyStore.generate] falls back to the TEE).
 */
@RunWith(AndroidJUnit4::class)
class HardwareBackedKeyTest {
    private val alias = "test-device-enroll-key"

    @Before
    fun clean() = HardwareKeyStore.delete(alias)

    @After
    fun tearDown() = HardwareKeyStore.delete(alias)

    @Test
    fun generate_producesA65ByteX963PublicPoint() {
        val key = HardwareKeyStore.generate(alias)
        val point = key.publicKeyX963()
        assertEquals(65, point.size)
        assertEquals(0x04, point[0].toInt() and 0xFF)
    }

    @Test
    fun generatedKeyIsNonExportable() {
        HardwareKeyStore.generate(alias)
        val loaded = HardwareKeyStore.load(alias)
        assertNotNull(loaded)
        // AndroidKeyStore private keys have no exportable encoding — the material never leaves HW.
        assertNull("AndroidKeyStore key must expose no encoded form", loaded!!.keyHandle.encoded)
    }

    @Test
    fun csrSignedByHardwareKeySelfVerifies() {
        val key = HardwareKeyStore.generate(alias)

        val der = CertificateSigningRequest.der("t1-android", key)

        // Re-parse the CertificationRequestInfo + signature and verify with the embedded public key.
        val outer = TestDer.read(der, 0)!!
        val parts = TestDer.children(der, outer)
        val info = der.copyOfRange(parts[0].start, parts[0].end)
        val bitString = parts[2]
        val signature = der.copyOfRange(bitString.valueStart + 1, bitString.valueEnd)

        // Rebuild a JCA public key from the X9.63 point to run the same crypto check the server does.
        val point = key.publicKeyX963()
        val pub = X963PublicKeys.p256(point)
        val ok = Signature.getInstance("SHA256withECDSA").apply {
            initVerify(pub)
            update(info)
        }.verify(signature)
        assertTrue("hardware-signed CSR must self-verify", ok)
    }

    @Test
    fun loadAfterGenerateReturnsAKeyWithTheSamePublicPoint() {
        val generated = HardwareKeyStore.generate(alias)
        val reloaded = HardwareKeyStore.load(alias)
        assertNotNull(reloaded)
        assertArrayEquals(generated.publicKeyX963(), reloaded!!.publicKeyX963())
    }

    @Test
    fun loadReturnsNullWhenNoKeyExists() {
        assertNull(HardwareKeyStore.load("absent-alias-xyz"))
    }
}

/** Reconstruct a P-256 public key from an X9.63 uncompressed point, for on-device signature checks. */
private object X963PublicKeys {
    fun p256(point: ByteArray): java.security.PublicKey {
        val params = java.security.AlgorithmParameters.getInstance("EC").apply {
            init(java.security.spec.ECGenParameterSpec("secp256r1"))
        }
        val spec = params.getParameterSpec(java.security.spec.ECParameterSpec::class.java)
        val x = java.math.BigInteger(1, point.copyOfRange(1, 33))
        val y = java.math.BigInteger(1, point.copyOfRange(33, 65))
        val pubSpec = java.security.spec.ECPublicKeySpec(java.security.spec.ECPoint(x, y), spec)
        return java.security.KeyFactory.getInstance("EC").generatePublic(pubSpec)
    }
}

/** A throwaway canonical-DER reader for structural assertions (device-side mirror of the JVM test). */
private 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, start, index, 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
    }
}
