"""Network configuration models."""

from __future__ import annotations

import ipaddress
from enum import Enum
from typing import TYPE_CHECKING, Annotated

from pydantic import BaseModel, Field, field_validator

if TYPE_CHECKING:
    from gaol.models.container import NetworkConfig


class Protocol(str, Enum):
    """Network protocol for port mappings."""

    TCP = "tcp"
    UDP = "udp"
    BOTH = "both"  # Convenience for mapping both TCP and UDP


class IPVersion(str, Enum):
    """IP version for network operations."""

    IPV4 = "ipv4"
    IPV6 = "ipv6"
    DUAL = "dual"  # Both IPv4 and IPv6


class PortMapping(BaseModel):
    """A single port mapping with protocol support."""

    host_port: Annotated[int, Field(ge=1, le=65535)]
    container_port: Annotated[int, Field(ge=1, le=65535)]
    protocol: Protocol = Protocol.TCP
    host_ip: str = "0.0.0.0"  # Bind address (supports IPv6 with "::")

    @field_validator("host_ip")
    @classmethod
    def validate_ip(cls, v: str) -> str:
        """Validate the host IP address."""
        if v not in ("0.0.0.0", "::"):
            ipaddress.ip_address(v)  # Validates IP format
        return v

    @property
    def is_privileged(self) -> bool:
        """Check if this mapping requires privileged ports."""
        return self.host_port < 1024


class PortMappingList(BaseModel):
    """Collection of port mappings with validation."""

    mappings: list[PortMapping] = Field(default_factory=list)

    def get_host_ports(self, protocol: Protocol | None = None) -> set[int]:
        """Get all host ports, optionally filtered by protocol."""
        if protocol is None:
            return {m.host_port for m in self.mappings}
        return {
            m.host_port
            for m in self.mappings
            if m.protocol == protocol or m.protocol == Protocol.BOTH
        }

    def has_conflict(self, other: PortMappingList) -> list[tuple[int, Protocol]]:
        """Check for conflicts with another mapping list."""
        conflicts = []
        for m in self.mappings:
            for o in other.mappings:
                if m.host_port == o.host_port and (
                    m.protocol == o.protocol
                    or Protocol.BOTH in (m.protocol, o.protocol)
                ):
                    conflicts.append((m.host_port, m.protocol))
        return conflicts

    def add(self, mapping: PortMapping) -> None:
        """Add a port mapping."""
        self.mappings.append(mapping)

    def __len__(self) -> int:
        """Return number of mappings."""
        return len(self.mappings)

    def __iter__(self):
        """Iterate over mappings."""
        return iter(self.mappings)


class DNSConfig(BaseModel):
    """DNS configuration for containers."""

    nameservers: list[str] = Field(default_factory=list)
    search_domains: list[str] = Field(default_factory=list)
    options: list[str] = Field(default_factory=list)  # e.g., ["ndots:5", "timeout:2"]

    @field_validator("nameservers")
    @classmethod
    def validate_nameservers(cls, v: list[str]) -> list[str]:
        """Validate DNS server addresses."""
        for ns in v:
            try:
                ipaddress.ip_address(ns)
            except ValueError as e:
                raise ValueError(f"Invalid DNS server address: {ns}") from e
        return v

    def to_resolv_conf(self) -> str:
        """Generate resolv.conf content."""
        lines = []
        for ns in self.nameservers:
            lines.append(f"nameserver {ns}")
        if self.search_domains:
            lines.append(f"search {' '.join(self.search_domains)}")
        if self.options:
            lines.append(f"options {' '.join(self.options)}")
        return "\n".join(lines) + "\n" if lines else ""


class BridgeConfig(BaseModel):
    """Bridge network configuration."""

    name: str = "br0"
    ip_address: str | None = None  # e.g., "192.168.100.1/24"
    dhcp_enabled: bool = False
    nat_enabled: bool = True
    ipv6_enabled: bool = False
    ipv6_address: str | None = None  # e.g., "fd00::1/64"


class NetworkConfigV2(BaseModel):
    """Enhanced network configuration (backward compatible)."""

    mode: str = "shared"  # Use str to avoid circular import with NetworkMode
    ports: PortMappingList = Field(default_factory=PortMappingList)
    dns: DNSConfig = Field(default_factory=DNSConfig)
    hostname: str | None = None
    bridge: BridgeConfig | None = None
    ip_version: IPVersion = IPVersion.IPV4

    @classmethod
    def from_legacy(cls, legacy: NetworkConfig) -> NetworkConfigV2:
        """Convert from legacy NetworkConfig."""
        mappings = [
            PortMapping(host_port=hp, container_port=cp)
            for hp, cp in legacy.ports.items()
        ]
        return cls(
            mode=legacy.mode.value,
            ports=PortMappingList(mappings=mappings),
            dns=DNSConfig(nameservers=legacy.dns),
            hostname=legacy.hostname,
        )

    def to_legacy_ports(self) -> dict[int, int]:
        """Convert port mappings back to legacy format (TCP only)."""
        return {
            m.host_port: m.container_port
            for m in self.ports.mappings
            if m.protocol in (Protocol.TCP, Protocol.BOTH)
        }
