"""DNS configuration helpers."""

from __future__ import annotations

import ipaddress
import socket
from pathlib import Path

from gaol.networking.models import DNSConfig


class DNSError(Exception):
    """DNS configuration error."""

    pass


class InvalidDNSServerError(DNSError):
    """Invalid DNS server address."""

    def __init__(self, address: str, reason: str = "") -> None:
        self.address = address
        self.reason = reason
        msg = f"Invalid DNS server: {address}"
        if reason:
            msg += f" ({reason})"
        super().__init__(msg)


def validate_dns_server(address: str, check_reachable: bool = False) -> bool:
    """Validate a DNS server address.

    Args:
        address: DNS server IP address
        check_reachable: Also check if server responds

    Returns:
        True if valid (and optionally reachable)

    Raises:
        InvalidDNSServerError: If address is invalid
    """
    # Validate IP format
    try:
        ip = ipaddress.ip_address(address)
    except ValueError as e:
        raise InvalidDNSServerError(address, "not a valid IP address") from e

    # Check if it's a reserved/invalid address for DNS
    if ip.is_loopback and address not in ("127.0.0.1", "::1"):
        raise InvalidDNSServerError(address, "loopback address")

    if ip.is_unspecified:
        raise InvalidDNSServerError(address, "unspecified address")

    if check_reachable:
        # Try to reach the DNS server with a simple query
        try:
            sock = socket.socket(
                socket.AF_INET6 if ip.version == 6 else socket.AF_INET,
                socket.SOCK_DGRAM,
            )
            sock.settimeout(2.0)
            sock.connect((address, 53))
            sock.close()
        except (TimeoutError, OSError) as e:
            raise InvalidDNSServerError(address, "not reachable") from e

    return True


def generate_resolv_conf(
    config: DNSConfig,
    include_comments: bool = True,
) -> str:
    """Generate resolv.conf content from DNS config.

    Args:
        config: DNS configuration
        include_comments: Include header comments

    Returns:
        resolv.conf file content
    """
    lines = []

    if include_comments:
        lines.append("# Generated by gaol")
        lines.append("# Do not edit manually")
        lines.append("")

    for ns in config.nameservers:
        lines.append(f"nameserver {ns}")

    if config.search_domains:
        lines.append(f"search {' '.join(config.search_domains)}")

    if config.options:
        lines.append(f"options {' '.join(config.options)}")

    return "\n".join(lines) + "\n"


def parse_resolv_conf(content: str) -> DNSConfig:
    """Parse resolv.conf content into DNSConfig.

    Args:
        content: resolv.conf file content

    Returns:
        DNSConfig object
    """
    nameservers = []
    search_domains: list[str] = []
    options: list[str] = []

    for line in content.splitlines():
        line = line.strip()
        if not line or line.startswith("#"):
            continue

        parts = line.split(None, 1)
        if len(parts) < 2:
            continue

        directive, value = parts
        directive = directive.lower()

        if directive == "nameserver":
            nameservers.append(value)
        elif directive == "search":
            search_domains = value.split()
        elif directive == "domain":
            # domain is like search but only one
            search_domains = [value]
        elif directive == "options":
            options = value.split()

    return DNSConfig(
        nameservers=nameservers,
        search_domains=search_domains,
        options=options,
    )


def get_system_dns() -> DNSConfig:
    """Get the system's current DNS configuration.

    Returns:
        DNSConfig from system resolv.conf
    """
    resolv_path = Path("/etc/resolv.conf")
    if resolv_path.exists():
        return parse_resolv_conf(resolv_path.read_text())
    return DNSConfig()


# Well-known public DNS servers
PUBLIC_DNS_SERVERS: dict[str, list[str]] = {
    "google": ["8.8.8.8", "8.8.4.4"],
    "google_ipv6": ["2001:4860:4860::8888", "2001:4860:4860::8844"],
    "cloudflare": ["1.1.1.1", "1.0.0.1"],
    "cloudflare_ipv6": ["2606:4700:4700::1111", "2606:4700:4700::1001"],
    "quad9": ["9.9.9.9", "149.112.112.112"],
    "quad9_ipv6": ["2620:fe::fe", "2620:fe::9"],
    "opendns": ["208.67.222.222", "208.67.220.220"],
}


def get_default_dns_config(
    ipv6: bool = False,
    provider: str = "cloudflare",
) -> DNSConfig:
    """Get a default DNS configuration using public DNS.

    Args:
        ipv6: Include IPv6 DNS servers
        provider: DNS provider (google, cloudflare, quad9, opendns)

    Returns:
        DNSConfig with public DNS servers
    """
    servers = list(PUBLIC_DNS_SERVERS.get(provider, PUBLIC_DNS_SERVERS["cloudflare"]))

    if ipv6:
        ipv6_key = f"{provider}_ipv6"
        if ipv6_key in PUBLIC_DNS_SERVERS:
            servers.extend(PUBLIC_DNS_SERVERS[ipv6_key])

    return DNSConfig(nameservers=servers)
