"""TLS certificate generation and fingerprint helpers for node-to-node comms."""

from __future__ import annotations

import datetime
import ipaddress
import os
import socket
import ssl
import stat
from pathlib import Path

_CERT_PATH = Path.home() / ".config" / "meshnet" / "node_cert.pem"
_KEY_PATH = Path.home() / ".config" / "meshnet" / "node_key.pem"


def generate_self_signed_cert(
    cert_path: Path | None = None,
    key_path: Path | None = None,
    common_name: str | None = None,
) -> tuple[Path, Path]:
    """Generate a self-signed RSA-2048 cert valid for 10 years.

    Returns (cert_path, key_path). Skips generation if both files already exist.
    """
    from cryptography import x509
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import rsa
    from cryptography.x509.oid import NameOID

    cert_p = cert_path or _CERT_PATH
    key_p = key_path or _KEY_PATH

    if cert_p.exists() and key_p.exists():
        return cert_p, key_p

    cert_p.parent.mkdir(parents=True, exist_ok=True)

    key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
    cn = common_name or socket.getfqdn()

    subject = issuer = x509.Name([
        x509.NameAttribute(NameOID.COMMON_NAME, cn),
        x509.NameAttribute(NameOID.ORGANIZATION_NAME, "meshnet-node"),
    ])

    san_list: list = [x509.DNSName(cn)]
    try:
        san_list.append(x509.IPAddress(ipaddress.IPv4Address(socket.gethostbyname(cn))))
    except (socket.gaierror, ValueError):
        pass
    san_list.append(x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")))

    cert = (
        x509.CertificateBuilder()
        .subject_name(subject)
        .issuer_name(issuer)
        .public_key(key.public_key())
        .serial_number(x509.random_serial_number())
        .not_valid_before(datetime.datetime.now(datetime.timezone.utc))
        .not_valid_after(datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=365 * 10))
        .add_extension(x509.SubjectAlternativeName(san_list), critical=False)
        .sign(key, hashes.SHA256())
    )

    key_pem = key.private_bytes(
        serialization.Encoding.PEM,
        serialization.PrivateFormat.TraditionalOpenSSL,
        serialization.NoEncryption(),
    )
    cert_pem = cert.public_bytes(serialization.Encoding.PEM)

    key_p.write_bytes(key_pem)
    cert_p.write_bytes(cert_pem)
    try:
        os.chmod(key_p, stat.S_IRUSR | stat.S_IWUSR)
    except OSError:
        pass

    return cert_p, key_p


def cert_fingerprint(cert_path: Path | None = None) -> str:
    """Return sha256 fingerprint of the cert as 'sha256:<hex>'."""
    from cryptography import x509
    from cryptography.hazmat.primitives import hashes

    p = cert_path or _CERT_PATH
    cert = x509.load_pem_x509_certificate(p.read_bytes())
    fp = cert.fingerprint(hashes.SHA256()).hex()
    return f"sha256:{fp}"


def make_server_ssl_context(
    cert_path: Path | None = None,
    key_path: Path | None = None,
) -> ssl.SSLContext:
    """Return an ssl.SSLContext for a server using our self-signed cert."""
    cert_p = cert_path or _CERT_PATH
    key_p = key_path or _KEY_PATH
    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    ctx.load_cert_chain(certfile=str(cert_p), keyfile=str(key_p))
    return ctx


def make_client_ssl_context(verify: bool = False) -> ssl.SSLContext:
    """Return a client SSLContext. verify=False for self-signed TOFU connections."""
    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
    if not verify:
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
    return ctx
