package wang.yaojia.webterm.clienttls

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertInstanceOf
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import java.io.IOException
import java.security.UnrecoverableKeyException
import javax.crypto.BadPaddingException

/** Ports iOS `PKCS12Importer` tests against a real keytool-minted `.p12` fixture (pure JVM). */
class Pkcs12ParseTest {
    @Test
    fun parsesLeafIdentityWithChainAndKey() {
        val identity = Pkcs12Parse.parse(Fixtures.leafP12(), Fixtures.PASSPHRASE)

        assertEquals("device", identity.alias, "must select the key entry, not the trusted-cert entry")
        assertEquals("RSA", identity.keyAlgorithm)
        assertEquals(Fixtures.LEAF_SUBJECT_CN, identity.summary().subjectCommonName)
    }

    @Test
    fun issuerChainExcludesTheLeafAndKeepsTheCa() {
        val identity = Pkcs12Parse.parse(Fixtures.leafP12(), Fixtures.PASSPHRASE)

        // Full chain is leaf → ca; issuerCertificates drops the leaf (mirrors iOS issuerChain).
        assertEquals(2, identity.fullChain.size)
        assertEquals(1, identity.issuerCertificates.size)
        assertEquals(identity.leafCertificate, identity.fullChain.first())
        val caSummary = CertificateSummaryReader.summarize(identity.issuerCertificates.first())
        assertEquals(Fixtures.LEAF_ISSUER_CN, caSummary.subjectCommonName, "issuer cert is the device CA")
    }

    @Test
    fun wrongPassphraseThrowsUnrecoverableKeyException() {
        assertThrows<UnrecoverableKeyException> {
            Pkcs12Parse.parse(Fixtures.leafP12(), Fixtures.WRONG_PASSPHRASE)
        }
    }

    @Test
    fun corruptBlobThrowsDecodeException() {
        val garbage = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8)
        assertThrows<Pkcs12DecodeException> {
            Pkcs12Parse.parse(garbage, Fixtures.PASSPHRASE)
        }
    }

    @Test
    fun bouncyCastleBadPaddingIsClassifiedAsWrongPassphraseNotCorrupt() {
        // On-device (BouncyCastle) a wrong passphrase surfaces as an IOException caused by
        // BadPaddingException, NOT UnrecoverableKeyException — it must still map to wrong-passphrase.
        val ioe = IOException("exception unwrapping private key", BadPaddingException("pad block corrupted"))

        assertInstanceOf(UnrecoverableKeyException::class.java, Pkcs12Parse.classifyLoadFailure(ioe))
    }

    @Test
    fun wrongPasswordMessageIsClassifiedAsWrongPassphrase() {
        // Some providers only report the failure in the message (no typed cause).
        assertInstanceOf(
            UnrecoverableKeyException::class.java,
            Pkcs12Parse.classifyLoadFailure(IOException("keystore password was incorrect")),
        )
    }

    @Test
    fun genuinelyUndecodableBlobStaysDecodeException() {
        // A structural DER failure carries no wrong-passphrase signal → stays a decode error.
        assertInstanceOf(
            Pkcs12DecodeException::class.java,
            Pkcs12Parse.classifyLoadFailure(IOException("DerInputStream.getLength(): lengthTag=127, too big")),
        )
    }

    @Test
    fun certsOnlyStoreThrowsNoClientIdentity() {
        assertThrows<NoClientIdentityException> {
            Pkcs12Parse.parse(Fixtures.trustP12(), Fixtures.PASSPHRASE)
        }
    }

    @Test
    fun privateKeyIsRecoverableWithCorrectPassphrase() {
        val identity = Pkcs12Parse.parse(Fixtures.leafP12(), Fixtures.PASSPHRASE)
        assertTrue(identity.privateKey.encoded.isNotEmpty(), "private key material is present")
    }
}
