"""
A tool that checks the TLS certificate on a local port and summarizes what it finds.

This module builds on the TLS probe to retrieve the server’s certificate, then
parses it into a clean structure that includes the subject, issuer, validity
dates, SAN entries, and signature algorithm. It also performs a very simple
validation step to see whether the certificate looks trustworthy.

Everything is restricted to local addresses for safety, so the agent never
connects to remote servers. The final result is returned as a
CertChainProbeResult, which includes both a high‑level summary (SUCCESS or
FAILURE) and the raw TLS probe output. This helps the agent understand whether
a service is using TLS correctly and gives developers clear signals when
something goes wrong, such as missing certificates or parsing errors.
"""


import ssl
import socket
from dataclasses import dataclass
from typing import Optional, List, Any

from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.x509.oid import ExtensionOID

from .utility import resolve_local_address
from .tls_probe import tls_probe


# -----------------------------
# Parsed certificate structure
# -----------------------------
@dataclass
class ParsedCert:
    subject: str
    issuer: str
    not_before: str
    not_after: str
    san: List[str]
    signature_algorithm: Optional[str]
    raw_der: bytes


# -----------------------------
# Meta-probe result structure
# -----------------------------
@dataclass
class CertChainProbeResult:
    # Pure summary fields
    status: str              # SUCCESS | FAILURE
    reason: str              # TLS_FAILED | NO_CERT | PARSE_ERROR | VALIDATION_ERROR | OK
    success: bool            # True if TLS succeeded and cert parsed
    validated: bool          # True if chain validated
    chain: List[ParsedCert]  # Parsed chain (leaf first)

    # Embedded raw TLS probe result
    tls: Any                 # Full tls_probe() return object


# -----------------------------
# Certificate parsing
# -----------------------------
def _parse_single_cert(der: bytes) -> ParsedCert:
    cert = x509.load_der_x509_certificate(der, default_backend())

    # Extract SANs safely
    try:
        san_ext = cert.extensions.get_extension_for_oid(
            ExtensionOID.SUBJECT_ALTERNATIVE_NAME
        )
        san_list = san_ext.value.get_values_for_type(x509.DNSName)  # type: ignore
    except Exception:
        san_list = []

    # Signature algorithm
    try:
        sig_alg = cert.signature_hash_algorithm.name  # type: ignore
    except Exception:
        sig_alg = None

    return ParsedCert(
        subject=cert.subject.rfc4514_string(),
        issuer=cert.issuer.rfc4514_string(),
        not_before=cert.not_valid_before.isoformat(),
        not_after=cert.not_valid_after.isoformat(),
        san=san_list,
        signature_algorithm=sig_alg,
        raw_der=der,
    )


# -----------------------------
# Minimal chain validation
# -----------------------------
def _validate_chain_minimal(chain: List[ParsedCert]) -> tuple[bool, Optional[str]]:
    if not chain:
        return False, "no certificates to validate"

    ctx = ssl.create_default_context()

    try:
        ctx.load_verify_locations(cadata=chain[0].raw_der.decode("latin1"))
    except Exception as e:
        return False, f"load error: {e}"

    try:
        s1, s2 = socket.socketpair()
        with ctx.wrap_socket(s1, server_hostname="dummy"):
            pass
    except Exception as e:
        return False, str(e)

    return True, None


# -----------------------------
# Meta-probe implementation
# -----------------------------
def cert_chain_probe(
    local_address_mode: str,
    port: int,
) -> CertChainProbeResult:
    """
    Certificate-chain meta probe.
    Top-level fields are pure summary.
    Full tls_probe() result is embedded.
    """

    # --- Run TLS probe ---
    tls = tls_probe(local_address_mode, port)

    # TLS failed entirely
    if not tls.success:
        return CertChainProbeResult(
            status="FAILURE",
            reason="TLS_FAILED",
            success=False,
            validated=False,
            chain=[],
            tls=tls,
        )

    # No certificate returned
    if not tls.certificate_der:
        return CertChainProbeResult(
            status="FAILURE",
            reason="NO_CERT",
            success=False,
            validated=False,
            chain=[],
            tls=tls,
        )

    # Parse leaf certificate
    try:
        leaf = _parse_single_cert(tls.certificate_der)
    except Exception:
        return CertChainProbeResult(
            status="FAILURE",
            reason="PARSE_ERROR",
            success=False,
            validated=False,
            chain=[],
            tls=tls,
        )

    chain = [leaf]

    # Validate chain
    validated, validation_error = _validate_chain_minimal(chain)

    if not validated:
        return CertChainProbeResult(
            status="FAILURE",
            reason=f"VALIDATION_ERROR: {validation_error}",
            success=True,
            validated=False,
            chain=chain,
            tls=tls,
        )

    # Success
    return CertChainProbeResult(
        status="SUCCESS",
        reason="OK",
        success=True,
        validated=True,
        chain=chain,
        tls=tls,
    )


# -----------------------------
# Manual test block
# -----------------------------
if __name__ == "__main__":
    host = "primary_ipv4"
    port = 443

    print(f"Running cert_chain_probe({host}, {port})...")
    result = cert_chain_probe(host, port)

    print("\n=== Certificate Chain Probe Summary ===")
    print(f"Status          : {result.status}")
    print(f"Reason          : {result.reason}")
    print(f"Success         : {result.success}")
    print(f"Validated       : {result.validated}")

    print("\n--- TLS Probe (raw) ---")
    print(result.tls)

    if result.chain:
        leaf = result.chain[0]
        print("\n--- Leaf Certificate ---")
        print(f"Subject          : {leaf.subject}")
        print(f"Issuer           : {leaf.issuer}")
        print(f"Not Before       : {leaf.not_before}")
        print(f"Not After        : {leaf.not_after}")
        print(f"SANs             : {leaf.san}")
        print(f"Signature Alg    : {leaf.signature_algorithm}")
