"""Network archetype model and resolver.

Archetypes are high-level networking patterns that agents pick from based on
use case. The resolver expands an archetype into a complete network plan:
nspawn config, nftables rulesets, networkd configs, policy routes, and DNS.

Archetypes:
    HOST       — Shares host network. No isolation.
    SHARED     — NAT through host veth (default nspawn).
    BRIDGED    — Own IP on LAN bridge.
    TAILSCALE  — Mesh via Tailscale inside the container.
    VPN_EXIT   — Private veth → VPN companion → internet.
    TOR_VPN_EXIT — Private veth → Tor → VPN → internet.
"""

from __future__ import annotations

from enum import Enum

from pydantic import BaseModel, Field

from gaol.networking.nftables import (
    NftRuleset,
    chain_forwarding_ruleset,
    kill_switch_ruleset,
)


class NetworkArchetype(str, Enum):
    """High-level network topology patterns."""

    HOST = "host"
    SHARED = "shared"
    BRIDGED = "bridged"
    TAILSCALE = "tailscale"
    VPN_EXIT = "vpn-exit"
    TOR_VPN_EXIT = "tor-vpn-exit"


class VethConfig(BaseModel):
    """Virtual ethernet pair configuration for a single hop."""

    host_iface: str  # e.g. "ve-research"
    container_ip: str  # e.g. "10.200.1.2"
    host_ip: str  # e.g. "10.200.1.1"
    subnet: str  # e.g. "10.200.1.0/30"
    prefix_len: int = 30


class PolicyRoute(BaseModel):
    """IP policy routing entry for chain networking."""

    table_id: int  # routing table number (200+)
    priority: int  # ip rule priority (100+)
    from_addr: str | None = None  # match source IP
    iif: str | None = None  # match incoming interface
    gateway: str | None = None  # default via for the table


class NetworkdConfig(BaseModel):
    """A systemd-networkd .network file to write on the host."""

    filename: str  # e.g. "80-container-ve-research.network"
    content: str  # INI content


class DNSConfig(BaseModel):
    """DNS configuration for the container."""

    nameservers: list[str]
    search_domains: list[str] = Field(default_factory=list)


class CompanionSpec(BaseModel):
    """Specification for a companion container in a chain."""

    name: str
    role: str  # "vpn", "tor"
    veth: VethConfig | None = None  # None if on bridge
    network_mode: str = "bridged"  # how the companion connects upstream
    bridge: str | None = None
    packages: list[str] = Field(default_factory=list)
    vpn_config_file: str | None = None
    kill_switch: bool = False


class ResolvedNetworkPlan(BaseModel):
    """Complete network plan produced by resolving an archetype.

    Contains everything needed to set up networking for a container
    and its companion containers (if any).
    """

    archetype: NetworkArchetype

    # nspawn settings
    nspawn_private: bool = True  # Private=yes (own network namespace)
    nspawn_veth: bool = True  # VirtualEthernet=yes
    nspawn_bridge: str | None = None  # Bridge=br0 etc.

    # Veth pair for the main container (None for host/shared/bridged)
    veth: VethConfig | None = None

    # Companion containers in the chain
    companions: list[CompanionSpec] = Field(default_factory=list)

    # Host-side networkd configs to write
    host_networkd: list[NetworkdConfig] = Field(default_factory=list)

    # nftables rulesets to apply
    nftables_rulesets: list[NftRuleset] = Field(default_factory=list)

    # Policy routing entries
    policy_routes: list[PolicyRoute] = Field(default_factory=list)

    # DNS for the main container
    dns: DNSConfig = Field(default_factory=lambda: DNSConfig(nameservers=["1.1.1.1"]))

    # Kill-switch inside the main container
    kill_switch: bool = False
    kill_switch_table: str | None = None


# ---------------------------------------------------------------------------
# Networkd config generators
# ---------------------------------------------------------------------------


def _host_networkd_for_veth(veth: VethConfig) -> NetworkdConfig:
    """Generate a host-side networkd .network file for a veth pair."""
    content = f"""\
[Match]
Name={veth.host_iface}

[Network]
Address={veth.host_ip}/{veth.prefix_len}
IPForward=yes
DHCPServer=no
"""
    filename = f"80-container-{veth.host_iface}.network"
    return NetworkdConfig(filename=filename, content=content)


# ---------------------------------------------------------------------------
# Archetype resolvers
# ---------------------------------------------------------------------------


def resolve_host() -> ResolvedNetworkPlan:
    """Resolve the HOST archetype — container shares host network."""
    return ResolvedNetworkPlan(
        archetype=NetworkArchetype.HOST,
        nspawn_private=False,
        nspawn_veth=False,
        dns=DNSConfig(nameservers=[]),  # inherited from host
    )


def resolve_shared() -> ResolvedNetworkPlan:
    """Resolve the SHARED archetype — NAT through host veth."""
    return ResolvedNetworkPlan(
        archetype=NetworkArchetype.SHARED,
        nspawn_private=True,
        nspawn_veth=True,
        dns=DNSConfig(nameservers=["1.1.1.1", "1.0.0.1"]),
    )


def resolve_bridged(bridge: str = "br0") -> ResolvedNetworkPlan:
    """Resolve the BRIDGED archetype — own IP on LAN bridge."""
    return ResolvedNetworkPlan(
        archetype=NetworkArchetype.BRIDGED,
        nspawn_private=True,
        nspawn_veth=True,
        nspawn_bridge=bridge,
        dns=DNSConfig(nameservers=["1.1.1.1", "1.0.0.1"]),
    )


def resolve_tailscale(bridge: str = "br0") -> ResolvedNetworkPlan:
    """Resolve the TAILSCALE archetype — bridged + Tailscale service inside."""
    plan = resolve_bridged(bridge)
    plan.archetype = NetworkArchetype.TAILSCALE
    return plan


def resolve_vpn_exit(
    container_name: str,
    veth: VethConfig,
    table_id: int,
    priority: int,
    vpn_container_name: str,
    vpn_config_file: str,
    vpn_dns: list[str] | None = None,
) -> ResolvedNetworkPlan:
    """Resolve the VPN_EXIT archetype — private veth → VPN companion → internet.

    Args:
        container_name: Name of the main container (≤12 chars).
        veth: Pre-allocated veth config from the subnet allocator.
        table_id: Routing table number (200+).
        priority: IP rule priority (100+).
        vpn_container_name: Name of the VPN companion container.
        vpn_config_file: Path to WireGuard/OpenVPN config on host.
        vpn_dns: DNS servers from VPN provider.
    """
    table_name = f"gaol_{container_name}"

    # Forwarding + masquerade ruleset on the host
    fwd_ruleset = chain_forwarding_ruleset(
        table_name, veth.host_iface, veth.subnet
    )

    # Kill-switch ruleset inside the container
    ks_ruleset = kill_switch_ruleset(
        f"{table_name}_ks", veth.host_ip
    )

    return ResolvedNetworkPlan(
        archetype=NetworkArchetype.VPN_EXIT,
        nspawn_private=True,
        nspawn_veth=False,  # we manage our own veth
        veth=veth,
        companions=[
            CompanionSpec(
                name=vpn_container_name,
                role="vpn",
                network_mode="bridged",
                bridge="br0",
                packages=["wireguard-tools"],
                vpn_config_file=vpn_config_file,
                kill_switch=False,
            ),
        ],
        host_networkd=[_host_networkd_for_veth(veth)],
        nftables_rulesets=[fwd_ruleset, ks_ruleset],
        policy_routes=[
            # Container traffic uses dedicated routing table
            PolicyRoute(
                table_id=table_id,
                priority=priority,
                from_addr=veth.container_ip,
            ),
            PolicyRoute(
                table_id=table_id,
                priority=priority,
                iif=veth.host_iface,
            ),
        ],
        dns=DNSConfig(nameservers=vpn_dns or ["1.1.1.1"]),
        kill_switch=True,
        kill_switch_table=f"{table_name}_ks",
    )


def resolve_tor_vpn_exit(
    container_name: str,
    container_veth: VethConfig,
    tor_veth: VethConfig,
    container_table_id: int,
    tor_table_id: int,
    container_priority: int,
    tor_priority: int,
    tor_container_name: str,
    vpn_container_name: str,
    vpn_config_file: str,
) -> ResolvedNetworkPlan:
    """Resolve TOR_VPN_EXIT — private veth → Tor → VPN → internet.

    Args:
        container_name: Name of the main container.
        container_veth: Veth config for main container ↔ host.
        tor_veth: Veth config for tor container ↔ host.
        container_table_id: Routing table for main container.
        tor_table_id: Routing table for tor container.
        container_priority: IP rule priority for main container.
        tor_priority: IP rule priority for tor container.
        tor_container_name: Name of the Tor companion container.
        vpn_container_name: Name of the VPN companion container.
        vpn_config_file: Path to WireGuard config on host.
    """
    table_name = f"gaol_{container_name}"

    # Host forwarding for main container
    fwd_ruleset = chain_forwarding_ruleset(
        table_name, container_veth.host_iface, container_veth.subnet
    )

    # Host forwarding for tor container
    tor_table_name = f"gaol_{tor_container_name}"
    tor_fwd_ruleset = chain_forwarding_ruleset(
        tor_table_name, tor_veth.host_iface, tor_veth.subnet
    )

    # Kill-switch for main container (allow gateway + tor IP)
    ks_ruleset = kill_switch_ruleset(
        f"{table_name}_ks",
        container_veth.host_ip,
        allowed_ips=[tor_veth.container_ip],
    )

    # Kill-switch for tor container (allow its gateway)
    tor_ks_ruleset = kill_switch_ruleset(
        f"{tor_table_name}_ks",
        tor_veth.host_ip,
    )

    return ResolvedNetworkPlan(
        archetype=NetworkArchetype.TOR_VPN_EXIT,
        nspawn_private=True,
        nspawn_veth=False,
        veth=container_veth,
        companions=[
            CompanionSpec(
                name=tor_container_name,
                role="tor",
                veth=tor_veth,
                network_mode="private",
                packages=["tor"],
                kill_switch=True,
            ),
            CompanionSpec(
                name=vpn_container_name,
                role="vpn",
                network_mode="bridged",
                bridge="br0",
                packages=["wireguard-tools"],
                vpn_config_file=vpn_config_file,
                kill_switch=False,
            ),
        ],
        host_networkd=[
            _host_networkd_for_veth(container_veth),
            _host_networkd_for_veth(tor_veth),
        ],
        nftables_rulesets=[fwd_ruleset, tor_fwd_ruleset, ks_ruleset, tor_ks_ruleset],
        policy_routes=[
            # Main container → tor container
            PolicyRoute(
                table_id=container_table_id,
                priority=container_priority,
                from_addr=container_veth.container_ip,
            ),
            PolicyRoute(
                table_id=container_table_id,
                priority=container_priority,
                iif=container_veth.host_iface,
            ),
            # Tor container → vpn container
            PolicyRoute(
                table_id=tor_table_id,
                priority=tor_priority,
                from_addr=tor_veth.container_ip,
            ),
            PolicyRoute(
                table_id=tor_table_id,
                priority=tor_priority,
                iif=tor_veth.host_iface,
            ),
        ],
        # DNS goes to Tor's DNSPort (port 53 on tor container IP)
        dns=DNSConfig(nameservers=[tor_veth.container_ip]),
        kill_switch=True,
        kill_switch_table=f"{table_name}_ks",
    )
