package wang.yaojia.webterm.clienttls

import java.security.cert.X509Certificate
import java.time.Instant
import javax.naming.ldap.LdapName
import javax.security.auth.x500.X500Principal

/**
 * Display summary of a device certificate (C-iOS-3), shown on the install / rotation screen so the
 * user can confirm *which* cert is active and *when* it expires before relying on it. Faithful port
 * of iOS `ClientCertificateSummary`.
 *
 * @property subjectCommonName subject CN — the device/leaf CN (e.g. `t1-android`); null if absent.
 * @property issuerCommonName  issuer CN — the device-CA CN (e.g. `webterm-device-ca`); null if absent.
 * @property notAfter          not-after instant; null if it could not be parsed.
 */
public data class CertificateSummary(
    val subjectCommonName: String?,
    val issuerCommonName: String?,
    val notAfter: Instant?,
) {
    /**
     * Expired relative to [now] (defaults to the current instant). Unknown expiry is treated as NOT
     * expired (fail-open for display only — the TLS stack, not this label, is the real gate),
     * matching iOS `isExpired`.
     */
    public fun isExpired(now: Instant = Instant.now()): Boolean {
        val end = notAfter ?: return false
        return end.isBefore(now)
    }
}

/**
 * Reads the display fields off an [X509Certificate].
 *
 * Unlike iOS (which hand-walks the DER because `SecCertificate` lacks the accessor on iOS), the JVM
 * `X509Certificate` already exposes subject/issuer principals and `notAfter`, so this is a thin
 * extraction. CN is pulled from the RFC2253 DN via `javax.naming.ldap.LdapName` (JVM stdlib), which
 * handles DN escaping/ordering correctly. Any parse miss degrades to `null` fields (the summary is
 * display-only; the TLS stack is the gate).
 */
public object CertificateSummaryReader {
    private const val COMMON_NAME_TYPE = "CN"

    public fun summarize(certificate: X509Certificate): CertificateSummary =
        CertificateSummary(
            subjectCommonName = commonName(certificate.subjectX500Principal),
            issuerCommonName = commonName(certificate.issuerX500Principal),
            notAfter = notAfterInstant(certificate),
        )

    private fun commonName(principal: X500Principal): String? =
        try {
            LdapName(principal.getName(X500Principal.RFC2253)).rdns
                .firstOrNull { it.type.equals(COMMON_NAME_TYPE, ignoreCase = true) }
                ?.value
                ?.toString()
        } catch (_: Exception) {
            null
        }

    private fun notAfterInstant(certificate: X509Certificate): Instant? =
        try {
            certificate.notAfter?.toInstant()
        } catch (_: Exception) {
            null
        }
}
