from datetime import datetime, timezone

import pytest

from nebula_cert_manager.models import (
    ClientInfo,
    Direction,
    FirewallConfig,
    HostConfig,
    HostDefaults,
    HostsConfig,
    LighthouseConfig,
    NetworkConfig,
    Protocol,
    Registry,
    SecurityGroup,
)


def test_lighthouse_config():
    lh = LighthouseConfig(
        nebula_ip="10.43.0.1", public_endpoints=["1.2.3.4"], listen_port=44300
    )
    assert lh.nebula_ip == "10.43.0.1"
    assert lh.public_endpoints == ["1.2.3.4"]
    assert lh.listen_port == 44300


def test_network_config():
    cfg = NetworkConfig(cidr="10.43.0.0/16")
    assert cfg.cidr == "10.43.0.0/16"


def test_client_info_defaults():
    client = ClientInfo(
        fingerprint="abc",
        cert="cert-pem",
        key="key-pem",
        issued_at=datetime(2026, 1, 1, tzinfo=timezone.utc),
        expires_at=datetime(2027, 1, 1, tzinfo=timezone.utc),
    )
    assert client.revoked is False
    assert client.revoked_at is None


def test_registry_roundtrip(sample_registry):
    data = sample_registry.model_dump(mode="json")
    restored = Registry.model_validate(data)
    assert restored.ca.fingerprint == sample_registry.ca.fingerprint
    assert restored.clients == {}


def test_registry_with_clients(sample_registry, sample_client):
    sample_registry.clients["test-laptop"] = [sample_client]
    data = sample_registry.model_dump(mode="json")
    restored = Registry.model_validate(data)
    assert "test-laptop" in restored.clients
    assert restored.clients["test-laptop"][0].fingerprint == "def456"


def test_find_by_fingerprint(sample_registry, sample_client):
    sample_registry.clients["test-laptop"] = [sample_client]
    result = sample_registry.find_by_fingerprint("def456")
    assert result is not None
    name, cert = result
    assert name == "test-laptop"
    assert cert.fingerprint == "def456"


def test_find_by_fingerprint_not_found(sample_registry):
    assert sample_registry.find_by_fingerprint("nonexistent") is None


def test_active_certs(sample_registry, sample_client):
    revoked_client = ClientInfo(
        fingerprint="rev789",
        cert="cert-pem",
        key="key-pem",
        issued_at=datetime(2025, 6, 1, tzinfo=timezone.utc),
        expires_at=datetime(2026, 6, 1, tzinfo=timezone.utc),
        revoked=True,
        revoked_at=datetime(2025, 12, 1, tzinfo=timezone.utc),
    )
    sample_registry.clients["test-laptop"] = [revoked_client, sample_client]
    active = sample_registry.active_certs("test-laptop")
    assert len(active) == 1
    assert active[0].fingerprint == "def456"


def test_active_certs_sorted_newest_first(sample_registry):
    older = ClientInfo(
        fingerprint="old111",
        cert="cert-pem",
        key="key-pem",
        issued_at=datetime(2025, 1, 1, tzinfo=timezone.utc),
        expires_at=datetime(2026, 1, 1, tzinfo=timezone.utc),
    )
    newer = ClientInfo(
        fingerprint="new222",
        cert="cert-pem",
        key="key-pem",
        issued_at=datetime(2026, 6, 1, tzinfo=timezone.utc),
        expires_at=datetime(2027, 6, 1, tzinfo=timezone.utc),
    )
    sample_registry.clients["test-laptop"] = [older, newer]
    active = sample_registry.active_certs("test-laptop")
    assert len(active) == 2
    assert active[0].fingerprint == "new222"
    assert active[1].fingerprint == "old111"


def test_newest_active_cert(sample_registry, sample_client):
    sample_registry.clients["test-laptop"] = [sample_client]
    newest = sample_registry.newest_active_cert("test-laptop")
    assert newest is not None
    assert newest.fingerprint == "def456"


def test_newest_active_cert_none(sample_registry):
    assert sample_registry.newest_active_cert("nonexistent") is None


def test_security_group():
    sg = SecurityGroup(port="any", proto=Protocol.ICMP, direction=Direction.ANY)
    assert sg.port == "any"
    assert sg.proto == Protocol.ICMP
    assert sg.direction == Direction.ANY


def test_security_group_with_int_port():
    sg = SecurityGroup(port=80, proto=Protocol.TCP, direction=Direction.IN)
    assert sg.port == 80
    assert sg.proto == Protocol.TCP
    assert sg.direction == Direction.IN


def test_firewall_config_defaults():
    cfg = FirewallConfig()
    assert cfg.security_groups == {}
    assert cfg.host_groups == {}
    assert cfg.firewall_rules == {}
    assert cfg.firewall_default == {}


def test_firewall_config_roundtrip():
    cfg = FirewallConfig(
        security_groups={
            "icmp": SecurityGroup(
                port="any", proto=Protocol.ICMP, direction=Direction.ANY
            ),
            "out-any": SecurityGroup(
                port="any", proto=Protocol.ANY, direction=Direction.OUT
            ),
        },
        host_groups={"laptop": ["admin"]},
        firewall_rules={
            "laptop": {
                "all": ["icmp", "out-any"],
                "admin": ["icmp"],
            }
        },
        firewall_default={"all": ["icmp", "out-any"]},
    )
    data = cfg.model_dump(mode="json")
    restored = FirewallConfig.model_validate(data)
    assert restored.security_groups["icmp"].proto == Protocol.ICMP
    assert restored.host_groups["laptop"] == ["admin"]
    assert restored.firewall_rules["laptop"]["all"] == ["icmp", "out-any"]
    assert restored.firewall_default["all"] == ["icmp", "out-any"]


def test_host_config():
    host = HostConfig(nebula_ip="10.43.0.1")
    assert host.nebula_ip == "10.43.0.1"
    assert host.public_endpoints is None
    assert host.tun_dev is None


def test_host_config_with_endpoints():
    host = HostConfig(
        nebula_ip="10.43.0.1",
        public_endpoints=["198.51.100.1:4242"],
        tun_dev="nebula2",
    )
    assert host.public_endpoints == ["198.51.100.1:4242"]
    assert host.tun_dev == "nebula2"


def test_host_defaults():
    defaults = HostDefaults()
    assert defaults.tun_dev is None

    defaults = HostDefaults(tun_dev="nebula1")
    assert defaults.tun_dev == "nebula1"


def test_hosts_config_unique_ips():
    config = HostsConfig(
        hosts={
            "host-a": HostConfig(nebula_ip="10.43.0.1"),
            "host-b": HostConfig(nebula_ip="10.43.0.2"),
        }
    )
    assert len(config.hosts) == 2


def test_hosts_config_duplicate_ips():
    with pytest.raises(ValueError, match="Duplicate nebula_ip '10.43.0.1'"):
        HostsConfig(
            hosts={
                "host-a": HostConfig(nebula_ip="10.43.0.1"),
                "host-b": HostConfig(nebula_ip="10.43.0.1"),
            }
        )
