"""Tests for system.py - interface detection, DNS discovery, architecture."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest

from cli.system import (
    NetworkInterface,
    _dns_from_resolv_conf,
    get_architecture,
    get_dns_servers,
    get_interfaces,
    get_os,
    is_root,
)


# ---------------------------------------------------------------------------
# Architecture detection
# ---------------------------------------------------------------------------


class TestGetArchitecture:
    @pytest.mark.parametrize(
        "machine, expected",
        [
            ("x86_64", "x86_64"),
            ("AMD64", "x86_64"),
            ("aarch64", "aarch64"),
            ("arm64", "aarch64"),
            ("armv7l", "arm"),
        ],
    )
    def test_known_architectures(self, machine, expected):
        with patch("platform.machine", return_value=machine):
            assert get_architecture() == expected

    def test_unknown_arch_passthrough(self):
        with patch("platform.machine", return_value="riscv64"):
            assert get_architecture() == "riscv64"


class TestGetOs:
    def test_linux(self):
        with patch("platform.system", return_value="Linux"):
            assert get_os() == "linux"

    def test_darwin(self):
        with patch("platform.system", return_value="Darwin"):
            assert get_os() == "darwin"


# ---------------------------------------------------------------------------
# Interface detection
# ---------------------------------------------------------------------------


class TestGetInterfaces:
    def test_returns_list_of_network_interfaces(self):
        mock_addrs = {
            "eth0": [
                MagicMock(family=2, address="192.168.1.100", netmask="255.255.255.0")
            ],
            "lo": [
                MagicMock(family=2, address="127.0.0.1", netmask="255.0.0.0")
            ],
        }
        with patch("psutil.net_if_addrs", return_value=mock_addrs):
            ifaces = get_interfaces()
        names = [i.name for i in ifaces]
        assert "eth0" in names
        assert "lo" not in names  # loopback filtered

    def test_cidr_computed_from_ip_and_netmask(self):
        import socket

        mock_addr = MagicMock(
            family=socket.AF_INET,
            address="10.0.1.50",
            netmask="255.255.255.0",
        )
        mock_addrs = {"eth0": [mock_addr]}
        with patch("psutil.net_if_addrs", return_value=mock_addrs):
            ifaces = get_interfaces()
        assert ifaces[0].cidrs == ["10.0.1.0/24"]

    def test_cgnat_address_with_netmask_normalized_to_100_64_slash_10(self):
        """A CGNAT address with a reported /8 netmask should suggest 100.64.0.0/10."""
        import socket

        mock_addr = MagicMock(
            family=socket.AF_INET,
            address="100.78.95.254",
            netmask="255.0.0.0",
        )
        mock_addrs = {"utun100": [mock_addr]}
        with patch("psutil.net_if_addrs", return_value=mock_addrs):
            ifaces = get_interfaces()
        assert ifaces[0].cidrs == ["100.64.0.0/10"]

    def test_cgnat_address_without_netmask_included(self):
        """macOS returns netmask=None for P2P utun interfaces; CGNAT addresses must still appear."""
        import socket

        mock_addr = MagicMock(
            family=socket.AF_INET,
            address="100.78.95.254",
            netmask=None,
        )
        mock_addrs = {"utun100": [mock_addr]}
        with patch("psutil.net_if_addrs", return_value=mock_addrs):
            ifaces = get_interfaces()
        assert len(ifaces) == 1
        assert ifaces[0].cidrs == ["100.64.0.0/10"]

    def test_virtual_interfaces_excluded(self):
        import socket

        mock_addrs = {
            "docker0": [MagicMock(family=socket.AF_INET, address="172.17.0.1", netmask="255.255.0.0")],
            "veth0abc": [MagicMock(family=socket.AF_INET, address="172.18.0.1", netmask="255.255.0.0")],
            "wg0": [MagicMock(family=socket.AF_INET, address="10.99.0.1", netmask="255.255.255.0")],
        }
        with patch("psutil.net_if_addrs", return_value=mock_addrs):
            ifaces = get_interfaces(include_virtual=False)
        names = [i.name for i in ifaces]
        assert "docker0" not in names
        assert "veth0abc" not in names
        assert "wg0" not in names  # wg prefix is also filtered

    def test_public_ip_interfaces_excluded(self):
        """Interfaces with public (non-RFC1918, non-CGNAT) IPs must not be suggested."""
        import socket

        mock_addrs = {
            "eth0": [MagicMock(family=socket.AF_INET, address="165.232.112.5", netmask="255.255.240.0")],
            "eth1": [MagicMock(family=socket.AF_INET, address="10.19.0.5", netmask="255.255.0.0")],
        }
        with patch("psutil.net_if_addrs", return_value=mock_addrs):
            ifaces = get_interfaces()
        names = [i.name for i in ifaces]
        assert "eth0" not in names   # public IP - excluded
        assert "eth1" in names       # private IP - included

    def test_skips_interfaces_without_ipv4(self):
        import socket

        # Use AF_INET6 (available cross-platform) as a non-IPv4 family
        mock_addrs = {
            "eth0": [
                MagicMock(family=socket.AF_INET6, address="fe80::1", netmask=None)
            ]
        }
        with patch("psutil.net_if_addrs", return_value=mock_addrs):
            ifaces = get_interfaces()
        assert ifaces == []


# ---------------------------------------------------------------------------
# DNS detection
# ---------------------------------------------------------------------------


class TestDnsFromResolvConf:
    def test_parses_nameservers(self, tmp_path):
        resolv = tmp_path / "resolv.conf"
        resolv.write_text("nameserver 8.8.8.8\nnameserver 1.1.1.1\n")
        with patch("builtins.open", patch("pathlib.Path.read_text", return_value=resolv.read_text())):
            pass
        # Directly test via monkeypatching Path.read_text
        with patch("pathlib.Path.read_text", return_value="nameserver 8.8.8.8\nnameserver 1.1.1.1\n"):
            servers = _dns_from_resolv_conf()
        assert "8.8.8.8" in servers
        assert "1.1.1.1" in servers

    def test_filters_loopback(self):
        content = "nameserver 127.0.0.1\nnameserver 127.0.0.53\nnameserver 192.168.1.1\n"
        with patch("pathlib.Path.read_text", return_value=content):
            servers = _dns_from_resolv_conf()
        assert "127.0.0.1" not in servers
        assert "127.0.0.53" not in servers
        assert "192.168.1.1" in servers

    def test_returns_empty_on_missing_file(self):
        with patch("pathlib.Path.read_text", side_effect=OSError):
            servers = _dns_from_resolv_conf()
        assert servers == []


