"""Tests for network archetype model and resolver."""

import pytest

from gaol.networking.archetypes import (
    NetworkArchetype,
    VethConfig,
    resolve_bridged,
    resolve_host,
    resolve_shared,
    resolve_tailscale,
    resolve_tor_vpn_exit,
    resolve_vpn_exit,
)


class TestNetworkArchetype:
    def test_enum_values(self):
        assert NetworkArchetype.HOST == "host"
        assert NetworkArchetype.VPN_EXIT == "vpn-exit"
        assert NetworkArchetype.TOR_VPN_EXIT == "tor-vpn-exit"


class TestResolveHost:
    def test_host_shares_network(self):
        plan = resolve_host()
        assert plan.archetype == NetworkArchetype.HOST
        assert plan.nspawn_private is False
        assert plan.nspawn_veth is False
        assert plan.veth is None
        assert plan.companions == []
        assert plan.nftables_rulesets == []
        assert plan.dns.nameservers == []  # inherited from host

    def test_host_no_kill_switch(self):
        plan = resolve_host()
        assert plan.kill_switch is False


class TestResolveShared:
    def test_shared_uses_veth(self):
        plan = resolve_shared()
        assert plan.archetype == NetworkArchetype.SHARED
        assert plan.nspawn_private is True
        assert plan.nspawn_veth is True
        assert plan.nspawn_bridge is None
        assert plan.dns.nameservers == ["1.1.1.1", "1.0.0.1"]


class TestResolveBridged:
    def test_bridged_uses_bridge(self):
        plan = resolve_bridged("br0")
        assert plan.archetype == NetworkArchetype.BRIDGED
        assert plan.nspawn_bridge == "br0"
        assert plan.nspawn_veth is True

    def test_bridged_custom_bridge(self):
        plan = resolve_bridged("br-lan")
        assert plan.nspawn_bridge == "br-lan"


class TestResolveTailscale:
    def test_tailscale_is_bridged_variant(self):
        plan = resolve_tailscale()
        assert plan.archetype == NetworkArchetype.TAILSCALE
        assert plan.nspawn_bridge == "br0"


class TestResolveVpnExit:
    def _make_veth(self) -> VethConfig:
        return VethConfig(
            host_iface="ve-research",
            container_ip="10.200.1.2",
            host_ip="10.200.1.1",
            subnet="10.200.1.0/30",
        )

    def test_vpn_exit_basic_structure(self):
        plan = resolve_vpn_exit(
            container_name="research",
            veth=self._make_veth(),
            table_id=200,
            priority=100,
            vpn_container_name="research-vpn",
            vpn_config_file="/etc/wireguard/mullvad.conf",
            vpn_dns=["10.64.0.1"],
        )
        assert plan.archetype == NetworkArchetype.VPN_EXIT
        assert plan.veth is not None
        assert plan.veth.container_ip == "10.200.1.2"
        assert plan.kill_switch is True

    def test_vpn_exit_has_companion(self):
        plan = resolve_vpn_exit(
            container_name="research",
            veth=self._make_veth(),
            table_id=200,
            priority=100,
            vpn_container_name="research-vpn",
            vpn_config_file="/etc/wireguard/mullvad.conf",
        )
        assert len(plan.companions) == 1
        assert plan.companions[0].role == "vpn"
        assert plan.companions[0].name == "research-vpn"

    def test_vpn_exit_has_nftables(self):
        plan = resolve_vpn_exit(
            container_name="research",
            veth=self._make_veth(),
            table_id=200,
            priority=100,
            vpn_container_name="research-vpn",
            vpn_config_file="/etc/wireguard/mullvad.conf",
        )
        # Should have forwarding ruleset + kill-switch ruleset
        assert len(plan.nftables_rulesets) == 2
        table_names = [r.table_name for r in plan.nftables_rulesets]
        assert "gaol_research" in table_names
        assert "gaol_research_ks" in table_names

    def test_vpn_exit_has_policy_routes(self):
        plan = resolve_vpn_exit(
            container_name="research",
            veth=self._make_veth(),
            table_id=200,
            priority=100,
            vpn_container_name="research-vpn",
            vpn_config_file="/etc/wireguard/mullvad.conf",
        )
        assert len(plan.policy_routes) == 2
        assert plan.policy_routes[0].table_id == 200
        assert plan.policy_routes[0].from_addr == "10.200.1.2"
        assert plan.policy_routes[1].iif == "ve-research"

    def test_vpn_exit_has_host_networkd(self):
        plan = resolve_vpn_exit(
            container_name="research",
            veth=self._make_veth(),
            table_id=200,
            priority=100,
            vpn_container_name="research-vpn",
            vpn_config_file="/etc/wireguard/mullvad.conf",
        )
        assert len(plan.host_networkd) == 1
        assert "ve-research" in plan.host_networkd[0].content
        assert plan.host_networkd[0].filename == "80-container-ve-research.network"

    def test_vpn_exit_dns(self):
        plan = resolve_vpn_exit(
            container_name="research",
            veth=self._make_veth(),
            table_id=200,
            priority=100,
            vpn_container_name="research-vpn",
            vpn_config_file="/etc/wireguard/mullvad.conf",
            vpn_dns=["10.64.0.1"],
        )
        assert plan.dns.nameservers == ["10.64.0.1"]

    def test_vpn_exit_renders_nftables(self):
        plan = resolve_vpn_exit(
            container_name="research",
            veth=self._make_veth(),
            table_id=200,
            priority=100,
            vpn_container_name="research-vpn",
            vpn_config_file="/etc/wireguard/mullvad.conf",
        )
        fwd = plan.nftables_rulesets[0].render()
        assert "chain forward" in fwd
        assert "masquerade" in fwd
        assert 'iif "ve-research"' in fwd

        ks = plan.nftables_rulesets[1].render()
        assert "policy drop" in ks
        assert "ip daddr 10.200.1.1 accept" in ks


class TestResolveTorVpnExit:
    def _make_veths(self) -> tuple[VethConfig, VethConfig]:
        container_veth = VethConfig(
            host_iface="ve-research",
            container_ip="10.200.1.2",
            host_ip="10.200.1.1",
            subnet="10.200.1.0/30",
        )
        tor_veth = VethConfig(
            host_iface="ve-tor",
            container_ip="10.200.2.2",
            host_ip="10.200.2.1",
            subnet="10.200.2.0/30",
        )
        return container_veth, tor_veth

    def test_tor_vpn_basic_structure(self):
        cv, tv = self._make_veths()
        plan = resolve_tor_vpn_exit(
            container_name="research",
            container_veth=cv,
            tor_veth=tv,
            container_table_id=200,
            tor_table_id=201,
            container_priority=100,
            tor_priority=101,
            tor_container_name="res-tor",
            vpn_container_name="res-vpn",
            vpn_config_file="/etc/wireguard/mullvad.conf",
        )
        assert plan.archetype == NetworkArchetype.TOR_VPN_EXIT
        assert len(plan.companions) == 2

    def test_tor_vpn_companions(self):
        cv, tv = self._make_veths()
        plan = resolve_tor_vpn_exit(
            container_name="research",
            container_veth=cv,
            tor_veth=tv,
            container_table_id=200,
            tor_table_id=201,
            container_priority=100,
            tor_priority=101,
            tor_container_name="res-tor",
            vpn_container_name="res-vpn",
            vpn_config_file="/etc/wireguard/mullvad.conf",
        )
        roles = [c.role for c in plan.companions]
        assert "tor" in roles
        assert "vpn" in roles

    def test_tor_vpn_has_four_rulesets(self):
        cv, tv = self._make_veths()
        plan = resolve_tor_vpn_exit(
            container_name="research",
            container_veth=cv,
            tor_veth=tv,
            container_table_id=200,
            tor_table_id=201,
            container_priority=100,
            tor_priority=101,
            tor_container_name="res-tor",
            vpn_container_name="res-vpn",
            vpn_config_file="/etc/wireguard/mullvad.conf",
        )
        # 2 forwarding + 2 kill-switch
        assert len(plan.nftables_rulesets) == 4

    def test_tor_vpn_dns_points_to_tor(self):
        cv, tv = self._make_veths()
        plan = resolve_tor_vpn_exit(
            container_name="research",
            container_veth=cv,
            tor_veth=tv,
            container_table_id=200,
            tor_table_id=201,
            container_priority=100,
            tor_priority=101,
            tor_container_name="res-tor",
            vpn_container_name="res-vpn",
            vpn_config_file="/etc/wireguard/mullvad.conf",
        )
        assert plan.dns.nameservers == ["10.200.2.2"]

    def test_tor_vpn_kill_switch_allows_tor_ip(self):
        cv, tv = self._make_veths()
        plan = resolve_tor_vpn_exit(
            container_name="research",
            container_veth=cv,
            tor_veth=tv,
            container_table_id=200,
            tor_table_id=201,
            container_priority=100,
            tor_priority=101,
            tor_container_name="res-tor",
            vpn_container_name="res-vpn",
            vpn_config_file="/etc/wireguard/mullvad.conf",
        )
        # Main container kill-switch should allow tor container IP
        ks = plan.nftables_rulesets[2]  # third ruleset is main ks
        rendered = ks.render()
        assert "10.200.2.2" in rendered

    def test_tor_vpn_has_four_policy_routes(self):
        cv, tv = self._make_veths()
        plan = resolve_tor_vpn_exit(
            container_name="research",
            container_veth=cv,
            tor_veth=tv,
            container_table_id=200,
            tor_table_id=201,
            container_priority=100,
            tor_priority=101,
            tor_container_name="res-tor",
            vpn_container_name="res-vpn",
            vpn_config_file="/etc/wireguard/mullvad.conf",
        )
        assert len(plan.policy_routes) == 4
        tables = {r.table_id for r in plan.policy_routes}
        assert tables == {200, 201}


class TestSpecNetworkArchetype:
    """Test the archetype field on SpecNetwork."""

    def test_archetype_none_by_default(self):
        from gaol.specs.models import SpecNetwork

        net = SpecNetwork()
        assert net.archetype is None

    def test_archetype_valid_values(self):
        from gaol.specs.models import SpecNetwork

        for arch in ["host", "shared", "bridged", "tailscale", "vpn-exit", "tor-vpn-exit"]:
            net = SpecNetwork(archetype=arch)
            assert net.archetype == arch

    def test_archetype_case_insensitive(self):
        from gaol.specs.models import SpecNetwork

        net = SpecNetwork(archetype="VPN-EXIT")
        assert net.archetype == "vpn-exit"

    def test_archetype_invalid_raises(self):
        from gaol.specs.models import SpecNetwork

        with pytest.raises(ValueError, match="Invalid network archetype"):
            SpecNetwork(archetype="invalid")

    def test_archetype_with_bridge(self):
        from gaol.specs.models import SpecNetwork

        net = SpecNetwork(archetype="bridged", bridge="br-lan")
        assert net.archetype == "bridged"
        assert net.bridge == "br-lan"
