from datetime import datetime
from enum import StrEnum
from typing import Self

from pydantic import BaseModel, model_validator


class LighthouseConfig(BaseModel):
    nebula_ip: str
    public_endpoints: list[str]
    listen_port: int


class NetworkConfig(BaseModel):
    cidr: str


class Protocol(StrEnum):
    ANY = "any"
    TCP = "tcp"
    UDP = "udp"
    ICMP = "icmp"


class Direction(StrEnum):
    IN = "in"
    OUT = "out"
    ANY = "any"


class SecurityGroup(BaseModel):
    port: str | int
    proto: Protocol
    direction: Direction


class FirewallConfig(BaseModel):
    security_groups: dict[str, SecurityGroup] = {}
    host_groups: dict[str, list[str]] = {}
    firewall_rules: dict[str, dict[str, list[str]]] = {}
    firewall_default: dict[str, list[str]] = {}


class CAInfo(BaseModel):
    name: str
    cert: str
    key: str
    fingerprint: str
    created_at: datetime


class ClientInfo(BaseModel):
    fingerprint: str
    cert: str
    key: str
    issued_at: datetime
    expires_at: datetime
    revoked: bool = False
    revoked_at: datetime | None = None


class HostDefaults(BaseModel):
    tun_dev: str | None = None
    listen_port: int | None = None
    relays: list[str] | None = None


class HostConfig(BaseModel):
    nebula_ip: str
    public_endpoints: list[str] | None = None
    tun_dev: str | None = None
    listen_port: int | None = None
    am_relay: bool = False
    relays: list[str] | None = None


class HostsConfig(BaseModel):
    defaults: HostDefaults = HostDefaults()
    hosts: dict[str, HostConfig] = {}

    @model_validator(mode="after")
    def _check_unique_ips(self) -> Self:
        seen: dict[str, str] = {}
        for name, host in self.hosts.items():
            ip = host.nebula_ip
            if ip in seen:
                raise ValueError(
                    f"Duplicate nebula_ip '{ip}': assigned to both '{seen[ip]}' and '{name}'"
                )
            seen[ip] = name
        return self


class NebulaConfig(BaseModel):
    network: NetworkConfig | None = None
    hosts: HostsConfig | None = None
    firewall: FirewallConfig | None = None


class Registry(BaseModel):
    ca: CAInfo
    clients: dict[str, list[ClientInfo]] = {}

    def find_by_fingerprint(self, fp: str) -> tuple[str, ClientInfo] | None:
        for name, certs in self.clients.items():
            for cert in certs:
                if cert.fingerprint == fp:
                    return (name, cert)
        return None

    def active_certs(self, name: str) -> list[ClientInfo]:
        certs = self.clients.get(name, [])
        return sorted(
            [c for c in certs if not c.revoked],
            key=lambda c: c.issued_at,
            reverse=True,
        )

    def newest_active_cert(self, name: str) -> ClientInfo | None:
        active = self.active_certs(name)
        return active[0] if active else None
