import argparse
import sys
from dataclasses import dataclass
from pathlib import Path

from nebula_cert_manager.config_gen import render_client_config
from nebula_cert_manager.exceptions import (
    CertManagerError,
    CertNotFoundError,
    FingerprintMismatchError,
    RegistryNotFoundError,
)
from nebula_cert_manager.firewall import resolve_firewall_rules
from nebula_cert_manager.hosts import resolve_host_overrides
from nebula_cert_manager.lighthouses import (
    extract_lighthouses,
    resolve_lighthouses_for_client,
)
from nebula_cert_manager.nebula_config import load_nebula_config
from nebula_cert_manager.registry import RegistryManager
from nebula_cert_manager.relay import resolve_relay_for_host


@dataclass
class ConfigResult:
    output_dir: Path
    is_revoked: bool


def add_parser(subparsers: argparse._SubParsersAction) -> None:
    parser = subparsers.add_parser(
        "config", help="Generate client config and cert/key files"
    )
    parser.add_argument("--name", required=True, help="Certificate name")
    parser.add_argument(
        "--output-dir", type=Path, required=True, help="Output directory"
    )
    parser.add_argument(
        "--fingerprint", default=None, help="Specific certificate fingerprint"
    )
    parser.add_argument(
        "--template", type=Path, default=None, help="Custom Jinja2 template"
    )
    parser.set_defaults(func=run)


def generate_config(
    registry_mgr: RegistryManager,
    name: str,
    output_dir: Path,
    fingerprint: str | None = None,
    template: Path | None = None,
) -> ConfigResult:
    """Generate client configuration files.

    Writes client.crt, client.key, config.yml to output_dir/name/.
    Returns ConfigResult; is_revoked flag lets caller print warning.
    Raises RegistryNotFoundError, CertNotFoundError, FingerprintMismatchError.
    """
    if not registry_mgr.exists():
        raise RegistryNotFoundError()

    registry = registry_mgr.load()

    is_revoked = False
    if fingerprint:
        result = registry.find_by_fingerprint(fingerprint)
        if result is None:
            raise CertNotFoundError(fingerprint=fingerprint)
        found_name, client = result
        if found_name != name:
            raise FingerprintMismatchError(fingerprint, name, found_name)
        is_revoked = client.revoked
    else:
        client = registry.newest_active_cert(name)
        if client is None:
            raise CertNotFoundError(name=name)

    nebula_cfg = load_nebula_config(registry_mgr.registry_dir)
    hosts_cfg = nebula_cfg.hosts if nebula_cfg is not None else None
    firewall_cfg = nebula_cfg.firewall if nebula_cfg is not None else None

    inbound_rules = None
    outbound_rules = None
    if firewall_cfg:
        inbound_rules, outbound_rules = resolve_firewall_rules(name, firewall_cfg)

    am_lighthouse = False
    lighthouse_list = []
    if hosts_cfg:
        lighthouses = extract_lighthouses(hosts_cfg)
        am_lighthouse, lighthouse_list = resolve_lighthouses_for_client(
            name, lighthouses
        )

    tun_dev = "nebula1"
    listen_port = None
    am_relay = False
    relay_ips: list[str] = []
    if hosts_cfg:
        overrides = resolve_host_overrides(name, hosts_cfg)
        if overrides.tun_dev is not None:
            tun_dev = overrides.tun_dev
        if overrides.listen_port is not None:
            listen_port = overrides.listen_port
        am_relay, relay_ips = resolve_relay_for_host(name, hosts_cfg)

    out_dir = output_dir / name
    out_dir.mkdir(parents=True, exist_ok=True)

    (out_dir / "client.crt").write_text(client.cert)
    (out_dir / "client.key").write_text(client.key)

    blocklist = [
        c.fingerprint for certs in registry.clients.values() for c in certs if c.revoked
    ]

    config_text = render_client_config(
        ca_cert=registry.ca.cert,
        client=client,
        lighthouses=lighthouse_list,
        template_path=template,
        inbound_rules=inbound_rules,
        outbound_rules=outbound_rules,
        am_lighthouse=am_lighthouse,
        tun_dev=tun_dev,
        blocklist=blocklist,
        listen_port=listen_port,
        am_relay=am_relay,
        relays=relay_ips,
    )
    (out_dir / "config.yml").write_text(config_text)

    return ConfigResult(output_dir=out_dir, is_revoked=is_revoked)


def run(args: argparse.Namespace) -> None:
    try:
        result = generate_config(
            args.registry_mgr,
            args.name,
            args.output_dir,
            args.fingerprint,
            args.template,
        )
    except CertManagerError as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)

    if result.is_revoked:
        print(
            f"Warning: certificate '{args.name}' ({args.fingerprint}) is revoked.",
            file=sys.stderr,
        )

    print(f"Config written to {result.output_dir}/")
    print("  client.crt  - client certificate")
    print("  client.key  - client private key")
    print("  config.yml  - Nebula configuration")
