"""mTLS certificate provisioning for ServiceBridge."""

from __future__ import annotations

import json
import logging
from dataclasses import dataclass
from typing import Optional
from urllib.error import URLError
from urllib.request import Request, urlopen

logger = logging.getLogger(__name__)


@dataclass
class TLSCredentials:
    """Holds mTLS key/cert/CA PEM bytes for gRPC channel credentials."""

    ca_cert: bytes
    client_cert: bytes
    client_key: bytes


def provision_tls(admin_url: str, service_name: str, service_key: str) -> Optional[TLSCredentials]:
    """Fetch CA cert and provision a signed client certificate from the server.

    Returns None if provisioning fails (caller should fall back to insecure).
    """
    try:
        ca_cert = _fetch_ca(admin_url)
        cert, key = _request_cert(admin_url, service_name, service_key)
        return TLSCredentials(ca_cert=ca_cert, client_cert=cert, client_key=key)
    except Exception as exc:
        logger.warning("mTLS provisioning failed (falling back to insecure): %s", exc)
        return None


def _fetch_ca(admin_url: str) -> bytes:
    resp = urlopen(f"{admin_url}/api/tls/ca.crt", timeout=10)
    if resp.status != 200:
        raise RuntimeError(f"fetch CA cert: status {resp.status}")
    return resp.read()


def _request_cert(admin_url: str, service_name: str, service_key: str) -> tuple[bytes, bytes]:
    """Generate a key pair, create a CSR, and request a signed certificate."""
    try:
        from cryptography import x509
        from cryptography.hazmat.primitives import hashes, serialization
        from cryptography.hazmat.primitives.asymmetric import rsa
        from cryptography.x509.oid import NameOID
    except ImportError:
        raise RuntimeError(
            "Install 'cryptography' package for mTLS support: pip install cryptography"
        )

    # Generate key
    key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
    key_pem = key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.TraditionalOpenSSL,
        encryption_algorithm=serialization.NoEncryption(),
    )

    # Generate CSR
    csr = (
        x509.CertificateSigningRequestBuilder()
        .subject_name(x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, service_name)]))
        .sign(key, hashes.SHA256())
    )
    csr_pem = csr.public_bytes(serialization.Encoding.PEM).decode()

    # Request signed cert
    body = json.dumps({"csr": csr_pem}).encode()
    req = Request(
        f"{admin_url}/api/tls/provision",
        data=body,
        headers={"Content-Type": "application/json", "x-service-key": service_key},
        method="POST",
    )
    try:
        resp = urlopen(req, timeout=15)
    except URLError as exc:
        raise RuntimeError(f"provision TLS: {exc}") from exc

    if resp.status != 200:
        raise RuntimeError(f"provision TLS: status {resp.status}")

    result = json.loads(resp.read())
    cert_pem = result.get("certificate", "").encode()
    if not cert_pem:
        raise RuntimeError("provision TLS: empty certificate in response")

    return cert_pem, key_pem
