"""Client-side TLS trust for VoxCast Server connections.

State of the art for a self-signed LAN server is trust-on-first-use (TOFU),
like SSH known_hosts: the first time the client sees an untrusted certificate
it shows the operator the fingerprint and, on confirmation, pins that exact
certificate. Later connections verify against the pinned cert (full hostname
validation still applies against the cert's IP/DNS SANs).

Three modes (setting `remote_tls_mode`):
  - "verify"   : normal CA validation (a real cert / internal PKI). Default.
  - "pinned"   : trust exactly the stored self-signed cert (`remote_cert_pem`).
  - "insecure" : no validation at all. Deliberate fallback, always warned about.

CRITICAL: every socket/SSL operation here is blocking and must run in an
executor (QtAsyncio has no async networking, see http_exec.py).
"""
from __future__ import annotations

import hashlib
import ssl

from loguru import logger


def fingerprint_of_pem(cert_pem: str) -> str:
    """Lowercase hex SHA-256 of the certificate DER. Matches the server's
    `--gen-cert` output so the operator can compare both ends."""
    der = ssl.PEM_cert_to_DER_cert(cert_pem)
    return hashlib.sha256(der).hexdigest()


def format_fingerprint(fp: str) -> str:
    """Group into colon-separated byte pairs for readable display."""
    return ":".join(fp[i:i + 2] for i in range(0, len(fp), 2)).upper()


def fetch_server_cert_sync(host: str, port: int, timeout: float = 5.0
                           ) -> tuple[str, str]:
    """Blocking: open a TLS connection WITHOUT verification and return the
    server's leaf certificate as (pem, sha256_fingerprint). Used only to show
    the operator a fingerprint to confirm (TOFU); never trusted on its own.

    Runs in an executor (blocking socket)."""
    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE
    import socket
    with socket.create_connection((host, port), timeout=timeout) as sock:
        with ctx.wrap_socket(sock, server_hostname=host) as ssock:
            der = ssock.getpeercert(binary_form=True)
    if not der:
        raise ssl.SSLError("server presented no certificate")
    pem = ssl.DER_cert_to_PEM_cert(der)
    return pem, hashlib.sha256(der).hexdigest()


def build_verify(mode: str, cert_pem: str = "") -> object:
    """Return the value to pass as `verify` to http_exec.arequest / httpx.

    - "pinned" with a stored PEM -> an SSLContext trusting exactly that cert.
    - "insecure" -> False (no validation; caller must have warned the user).
    - anything else (incl. "verify") -> True (normal CA validation).
    """
    if mode == "insecure":
        return False
    if mode == "pinned" and cert_pem.strip():
        ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
        # Trust exactly the pinned self-signed cert as its own anchor. Hostname
        # verification stays on: the cert's IP/DNS SAN must match the address.
        try:
            ctx.load_verify_locations(cadata=cert_pem)
        except ssl.SSLError as e:
            logger.warning("Pinned certificate unloadable ({}); "
                           "falling back to strict verify", e)
            return True
        ctx.check_hostname = True
        ctx.verify_mode = ssl.CERT_REQUIRED
        return ctx
    return True


def needs_tls(url: str) -> bool:
    return url.lower().startswith("https://")
