import Foundation
import Security

/// C-iOS-3 · Display summary of a device certificate, shown on the install /
/// rotation screen so the user can confirm *which* cert is active and *when*
/// it expires before relying on it.
public struct ClientCertificateSummary: Equatable, Sendable {
    /// Subject common name — the device/leaf CN (e.g. `t1-iphone`).
    public let subjectCommonName: String?
    /// Issuer common name — the device-CA CN (e.g. `webterm-device-ca`).
    public let issuerCommonName: String?
    /// Not-after date; `nil` if it could not be parsed.
    public let notAfter: Date?

    public init(subjectCommonName: String?, issuerCommonName: String?, notAfter: Date?) {
        self.subjectCommonName = subjectCommonName
        self.issuerCommonName = issuerCommonName
        self.notAfter = notAfter
    }

    /// 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).
    public func isExpired(asOf now: Date = Date()) -> Bool {
        guard let notAfter else { return false }
        return notAfter < now
    }
}

/// Reads the display fields off a `SecCertificate`.
///
/// Implemented as a minimal X.509 DER walk over `SecCertificateCopyData` rather
/// than `SecCertificateCopyValues`, because that API and its `kSecOID*` /
/// `kSecPropertyKey*` constants are **macOS-only** — unavailable on iOS, which
/// is the real deployment target. The DER walk is identical on both platforms
/// and unit-testable against the fixture. Any parse miss degrades to `nil`
/// fields (the summary is display-only; the TLS stack is the gate).
enum CertificateInspector {
    static func summary(of certificate: SecCertificate) -> ClientCertificateSummary {
        let der = [UInt8](SecCertificateCopyData(certificate) as Data)
        let fields = X509Fields.parse(der: der)
        return ClientCertificateSummary(
            subjectCommonName: fields?.subjectCommonName,
            issuerCommonName: fields?.issuerCommonName,
            notAfter: fields?.notAfter
        )
    }
}

// MARK: - Minimal ASN.1 DER reader

/// One tag-length-value element, as byte ranges into the source buffer.
private struct DERElement {
    let tag: UInt8
    let valueStart: Int
    let valueEnd: Int
}

private enum DER {
    /// DER tags used here.
    static let sequence: UInt8 = 0x30
    static let set: UInt8 = 0x31
    static let oid: UInt8 = 0x06
    static let contextTag0: UInt8 = 0xA0 // [0] EXPLICIT (X.509 version)
    static let utcTime: UInt8 = 0x17
    static let generalizedTime: UInt8 = 0x18

    /// Read a single TLV at `start`. Returns the element and the index just
    /// past it, or `nil` on any malformed length/overrun (defensive: the cert
    /// bytes come from the system but are still parsed as untrusted input).
    static func read(_ bytes: [UInt8], at start: Int) -> DERElement? {
        guard start >= 0, start + 1 < bytes.count else { return nil }
        let tag = bytes[start]
        var index = start + 1
        let firstLengthByte = bytes[index]
        index += 1
        var length = 0
        if firstLengthByte & 0x80 == 0 {
            length = Int(firstLengthByte)
        } else {
            let byteCount = Int(firstLengthByte & 0x7F)
            guard byteCount > 0, byteCount <= 4, index + byteCount <= bytes.count else {
                return nil
            }
            for _ in 0..<byteCount {
                length = (length << 8) | Int(bytes[index])
                index += 1
            }
        }
        let valueEnd = index + length
        guard length >= 0, valueEnd <= bytes.count else { return nil }
        return DERElement(tag: tag, valueStart: index, valueEnd: valueEnd)
    }

    /// Split a constructed value `[start, end)` into its immediate children.
    static func children(_ bytes: [UInt8], from start: Int, to end: Int) -> [DERElement] {
        var elements: [DERElement] = []
        var index = start
        while index < end, let element = read(bytes, at: index) {
            elements.append(element)
            index = element.valueEnd
        }
        return elements
    }
}

// MARK: - X.509 field extraction

private struct X509Fields {
    let subjectCommonName: String?
    let issuerCommonName: String?
    let notAfter: Date?

    /// CommonName attribute OID `2.5.4.3` → DER content bytes `55 04 03`.
    private static let commonNameOID: [UInt8] = [0x55, 0x04, 0x03]

    /// Certificate ::= SEQUENCE { tbsCertificate, signatureAlgorithm, signature }
    /// tbsCertificate ::= SEQUENCE { [0] version?, serialNumber, signature,
    ///   issuer Name, validity, subject Name, subjectPublicKeyInfo, ... }
    static func parse(der bytes: [UInt8]) -> X509Fields? {
        guard let certificate = DER.read(bytes, at: 0), certificate.tag == DER.sequence else {
            return nil
        }
        let certChildren = DER.children(
            bytes, from: certificate.valueStart, to: certificate.valueEnd
        )
        guard let tbs = certChildren.first, tbs.tag == DER.sequence else { return nil }

        var tbsChildren = DER.children(bytes, from: tbs.valueStart, to: tbs.valueEnd)
        // Optional EXPLICIT [0] version — drop it so the fixed fields align.
        if let first = tbsChildren.first, first.tag == DER.contextTag0 {
            tbsChildren.removeFirst()
        }
        // Fixed order after (optional) version: serialNumber, signature,
        // issuer, validity, subject, ...
        guard tbsChildren.count >= 5 else { return nil }
        let issuer = tbsChildren[2]
        let validity = tbsChildren[3]
        let subject = tbsChildren[4]

        return X509Fields(
            subjectCommonName: commonName(bytes, in: subject),
            issuerCommonName: commonName(bytes, in: issuer),
            notAfter: notAfterDate(bytes, in: validity)
        )
    }

    /// Name ::= SEQUENCE OF RDN(SET) OF AttributeTypeAndValue(SEQ{OID, value}).
    /// Returns the first CN value found.
    private static func commonName(_ bytes: [UInt8], in name: DERElement) -> String? {
        for rdn in DER.children(bytes, from: name.valueStart, to: name.valueEnd)
        where rdn.tag == DER.set {
            for atv in DER.children(bytes, from: rdn.valueStart, to: rdn.valueEnd)
            where atv.tag == DER.sequence {
                let parts = DER.children(bytes, from: atv.valueStart, to: atv.valueEnd)
                guard parts.count >= 2, parts[0].tag == DER.oid else { continue }
                let oid = Array(bytes[parts[0].valueStart..<parts[0].valueEnd])
                if oid == commonNameOID {
                    let value = Array(bytes[parts[1].valueStart..<parts[1].valueEnd])
                    // PrintableString / UTF8String both decode as UTF-8.
                    return String(bytes: value, encoding: .utf8)
                }
            }
        }
        return nil
    }

    /// Validity ::= SEQUENCE { notBefore Time, notAfter Time } — the 2nd child.
    private static func notAfterDate(_ bytes: [UInt8], in validity: DERElement) -> Date? {
        let times = DER.children(bytes, from: validity.valueStart, to: validity.valueEnd)
        guard times.count >= 2 else { return nil }
        let notAfter = times[1]
        let value = Array(bytes[notAfter.valueStart..<notAfter.valueEnd])
        guard let text = String(bytes: value, encoding: .ascii) else { return nil }
        return parseTime(text, tag: notAfter.tag)
    }

    private static func parseTime(_ text: String, tag: UInt8) -> Date? {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.timeZone = TimeZone(identifier: "UTC")
        // UTCTime: YYMMDDHHMMSSZ (used for years < 2050); GeneralizedTime:
        // YYYYMMDDHHMMSSZ. The 2-digit-year window covers the relevant range
        // (device certs live in the 2020s-2040s).
        formatter.dateFormat = tag == DER.utcTime ? "yyMMddHHmmss'Z'" : "yyyyMMddHHmmss'Z'"
        return formatter.date(from: text)
    }
}
