"""Tests for high-level network setup integration."""

from pathlib import Path
from unittest.mock import patch

import pytest

from gaol.networking.archetypes import NetworkArchetype
from gaol.networking.setup import resolve_from_spec, setup_network, teardown_network
from gaol.specs.models import ContainerSpec, SpecNetwork, SpecPrivacy, SpecVPN


class TestResolveFromSpec:
    def test_no_archetype_defaults_to_shared(self):
        spec = ContainerSpec(name="test", runtime="nspawn")
        plan = resolve_from_spec(spec)
        assert plan.archetype == NetworkArchetype.SHARED

    def test_no_archetype_host_mode(self):
        spec = ContainerSpec(
            name="test",
            runtime="nspawn",
            network=SpecNetwork(mode="host"),
        )
        plan = resolve_from_spec(spec)
        assert plan.archetype == NetworkArchetype.HOST

    def test_no_archetype_bridged_mode(self):
        spec = ContainerSpec(
            name="test",
            runtime="nspawn",
            network=SpecNetwork(mode="bridged"),
        )
        plan = resolve_from_spec(spec)
        assert plan.archetype == NetworkArchetype.BRIDGED

    def test_host_archetype(self):
        spec = ContainerSpec(
            name="test",
            runtime="nspawn",
            network=SpecNetwork(archetype="host"),
        )
        plan = resolve_from_spec(spec)
        assert plan.archetype == NetworkArchetype.HOST
        assert plan.nspawn_private is False

    def test_shared_archetype(self):
        spec = ContainerSpec(
            name="test",
            runtime="nspawn",
            network=SpecNetwork(archetype="shared"),
        )
        plan = resolve_from_spec(spec)
        assert plan.archetype == NetworkArchetype.SHARED

    def test_bridged_archetype(self):
        spec = ContainerSpec(
            name="test",
            runtime="nspawn",
            network=SpecNetwork(archetype="bridged", bridge="br-lan"),
        )
        plan = resolve_from_spec(spec)
        assert plan.archetype == NetworkArchetype.BRIDGED
        assert plan.nspawn_bridge == "br-lan"

    def test_tailscale_archetype(self):
        spec = ContainerSpec(
            name="test",
            runtime="nspawn",
            network=SpecNetwork(archetype="tailscale"),
        )
        plan = resolve_from_spec(spec)
        assert plan.archetype == NetworkArchetype.TAILSCALE

    def test_vpn_exit_archetype(self, tmp_path: Path):
        state = tmp_path / "state.yaml"
        spec = ContainerSpec(
            name="research",
            runtime="nspawn",
            network=SpecNetwork(archetype="vpn-exit"),
            privacy=SpecPrivacy(
                vpn=SpecVPN(
                    config_file="/etc/wireguard/mullvad.conf",
                    dns_servers=["10.64.0.1"],
                ),
            ),
        )
        plan = resolve_from_spec(spec, state_path=state)
        assert plan.archetype == NetworkArchetype.VPN_EXIT
        assert plan.veth is not None
        assert plan.veth.container_ip == "10.200.1.2"
        assert len(plan.companions) == 1
        assert plan.companions[0].name == "svc-research-vpn"
        assert plan.dns.nameservers == ["10.64.0.1"]

    def test_vpn_exit_without_vpn_config_raises(self):
        spec = ContainerSpec(
            name="test",
            runtime="nspawn",
            network=SpecNetwork(archetype="vpn-exit"),
        )
        with pytest.raises(ValueError, match="privacy.vpn configuration"):
            resolve_from_spec(spec)

    def test_tor_vpn_exit_archetype(self, tmp_path: Path):
        state = tmp_path / "state.yaml"
        spec = ContainerSpec(
            name="research",
            runtime="nspawn",
            network=SpecNetwork(archetype="tor-vpn-exit"),
            privacy=SpecPrivacy(
                vpn=SpecVPN(config_file="/etc/wireguard/mullvad.conf"),
            ),
        )
        plan = resolve_from_spec(spec, state_path=state)
        assert plan.archetype == NetworkArchetype.TOR_VPN_EXIT
        assert len(plan.companions) == 2
        roles = {c.role for c in plan.companions}
        assert roles == {"tor", "vpn"}
        # DNS should point to tor container
        assert plan.dns.nameservers == ["10.200.2.2"]

    def test_tor_vpn_allocates_two_subnets(self, tmp_path: Path):
        state = tmp_path / "state.yaml"
        spec = ContainerSpec(
            name="research",
            runtime="nspawn",
            network=SpecNetwork(archetype="tor-vpn-exit"),
            privacy=SpecPrivacy(
                vpn=SpecVPN(config_file="/etc/wireguard/mullvad.conf"),
            ),
        )
        plan = resolve_from_spec(spec, state_path=state)
        # Main container on 10.200.1.x, tor on 10.200.2.x
        assert plan.veth is not None
        assert plan.veth.subnet == "10.200.1.0/30"
        tor_companion = next(c for c in plan.companions if c.role == "tor")
        assert tor_companion.veth is not None
        assert tor_companion.veth.subnet == "10.200.2.0/30"


class TestSetupNetwork:
    @patch("gaol.networking.executor.subprocess.run")
    def test_setup_shared_is_noop(self, mock_run):
        spec = ContainerSpec(name="test", runtime="nspawn")
        plan, result = setup_network(spec)
        assert result.ok is True
        mock_run.assert_not_called()

    @patch("gaol.networking.executor.subprocess.run")
    def test_setup_vpn_exit(self, mock_run, tmp_path: Path):
        from unittest.mock import MagicMock

        m = MagicMock()
        m.returncode = 0
        m.stdout = ""
        m.stderr = ""
        mock_run.return_value = m

        state = tmp_path / "state.yaml"
        spec = ContainerSpec(
            name="test",
            runtime="nspawn",
            network=SpecNetwork(archetype="vpn-exit"),
            privacy=SpecPrivacy(
                vpn=SpecVPN(config_file="/etc/wireguard/test.conf"),
            ),
        )
        plan, result = setup_network(spec, state_path=state)
        assert result.ok is True
        assert plan.archetype == NetworkArchetype.VPN_EXIT
        assert mock_run.call_count > 0


class TestTeardownNetwork:
    @patch("gaol.networking.executor.subprocess.run")
    def test_teardown_shared_is_noop(self, mock_run):
        spec = ContainerSpec(name="test", runtime="nspawn")
        _, result = teardown_network(spec)
        assert result.ok is True
        mock_run.assert_not_called()

    @patch("gaol.networking.executor.subprocess.run")
    def test_teardown_vpn_releases_allocation(self, mock_run, tmp_path: Path):
        from unittest.mock import MagicMock

        from gaol.networking.allocator import get_allocation

        m = MagicMock()
        m.returncode = 0
        m.stdout = ""
        m.stderr = ""
        mock_run.return_value = m

        state = tmp_path / "state.yaml"
        spec = ContainerSpec(
            name="test",
            runtime="nspawn",
            network=SpecNetwork(archetype="vpn-exit"),
            privacy=SpecPrivacy(
                vpn=SpecVPN(config_file="/etc/wireguard/test.conf"),
            ),
        )

        # Setup first (creates allocation)
        setup_network(spec, state_path=state)
        assert get_allocation("test", state_path=state) is not None

        # Teardown releases allocation
        teardown_network(spec, state_path=state)
        assert get_allocation("test", state_path=state) is None
