"""WireGuard backend.

This module provides the WireGuardBackend class for managing WireGuard
interface lifecycle - creating, configuring, and managing interfaces.
"""

from __future__ import annotations

import shutil
import subprocess
from pathlib import Path

from gaol.privacy.exceptions import WireGuardNotFoundError


class WireGuardBackend:
    """WireGuard backend.

    Manages WireGuard interfaces and key operations.
    """

    def __init__(self, state_dir: Path | None = None) -> None:
        """Initialize the WireGuard backend.

        Args:
            state_dir: Directory for WireGuard state files. Defaults to
                       ~/.local/state/gaol/wireguard
        """
        if state_dir is None:
            state_dir = Path.home() / ".local" / "state" / "gaol" / "wireguard"
        self.state_dir = state_dir
        self.state_dir.mkdir(parents=True, exist_ok=True)

    def is_available(self) -> bool:
        """Check if WireGuard tools are installed.

        Returns:
            True if wg and wg-quick are available.
        """
        return shutil.which("wg") is not None and shutil.which("wg-quick") is not None

    def _check_available(self) -> None:
        """Check if WireGuard is available, raise if not.

        Raises:
            WireGuardNotFoundError: If WireGuard tools not found.
        """
        if not self.is_available():
            raise WireGuardNotFoundError()

    def generate_keypair(self) -> tuple[str, str]:
        """Generate a WireGuard keypair.

        Returns:
            Tuple of (private_key, public_key).

        Raises:
            WireGuardNotFoundError: If wg not found.
        """
        self._check_available()

        # Generate private key
        result = subprocess.run(
            ["wg", "genkey"],
            capture_output=True,
            text=True,
        )
        if result.returncode != 0:
            raise RuntimeError(f"Failed to generate private key: {result.stderr}")
        private_key = result.stdout.strip()

        # Derive public key
        result = subprocess.run(
            ["wg", "pubkey"],
            input=private_key,
            capture_output=True,
            text=True,
        )
        if result.returncode != 0:
            raise RuntimeError(f"Failed to derive public key: {result.stderr}")
        public_key = result.stdout.strip()

        return private_key, public_key

    def generate_preshared_key(self) -> str:
        """Generate a WireGuard preshared key.

        Returns:
            Preshared key.

        Raises:
            WireGuardNotFoundError: If wg not found.
        """
        self._check_available()

        result = subprocess.run(
            ["wg", "genpsk"],
            capture_output=True,
            text=True,
        )
        if result.returncode != 0:
            raise RuntimeError(f"Failed to generate preshared key: {result.stderr}")
        return result.stdout.strip()

    def create_interface(
        self,
        name: str,
        private_key: str,
        address: str,
        listen_port: int = 51820,
    ) -> Path:
        """Create a WireGuard interface configuration.

        Args:
            name: Interface name.
            private_key: Interface private key.
            address: Interface address with CIDR (e.g., 10.200.0.1/24).
            listen_port: Listen port for incoming connections.

        Returns:
            Path to the generated configuration file.
        """
        config_dir = self.state_dir / name
        config_dir.mkdir(parents=True, exist_ok=True)

        config_content = f"""[Interface]
PrivateKey = {private_key}
Address = {address}
ListenPort = {listen_port}
"""

        config_file = config_dir / f"{name}.conf"
        config_file.write_text(config_content)
        config_file.chmod(0o600)

        return config_file

    def add_peer_to_config(
        self,
        config_file: Path,
        public_key: str,
        allowed_ips: list[str],
        endpoint: str | None = None,
        persistent_keepalive: int = 25,
        preshared_key: str | None = None,
    ) -> None:
        """Add a peer to a WireGuard configuration file.

        Args:
            config_file: Path to configuration file.
            public_key: Peer's public key.
            allowed_ips: Allowed IP ranges for this peer.
            endpoint: Peer's endpoint (host:port).
            persistent_keepalive: Keepalive interval in seconds.
            preshared_key: Optional preshared key.
        """
        peer_config = f"""
[Peer]
PublicKey = {public_key}
AllowedIPs = {", ".join(allowed_ips)}
"""
        if endpoint:
            peer_config += f"Endpoint = {endpoint}\n"
        if persistent_keepalive > 0:
            peer_config += f"PersistentKeepalive = {persistent_keepalive}\n"
        if preshared_key:
            peer_config += f"PresharedKey = {preshared_key}\n"

        with open(config_file, "a") as f:
            f.write(peer_config)

    def interface_up(self, config_file: Path) -> None:
        """Bring up a WireGuard interface.

        Args:
            config_file: Path to configuration file.
        """
        self._check_available()

        result = subprocess.run(
            ["sudo", "wg-quick", "up", str(config_file)],
            capture_output=True,
            text=True,
        )
        if result.returncode != 0:
            raise RuntimeError(f"Failed to bring up interface: {result.stderr}")

    def interface_down(self, config_file: Path) -> None:
        """Bring down a WireGuard interface.

        Args:
            config_file: Path to configuration file.
        """
        self._check_available()

        result = subprocess.run(
            ["sudo", "wg-quick", "down", str(config_file)],
            capture_output=True,
            text=True,
        )
        # Don't raise on failure - interface might already be down
        # if result.returncode != 0:
        #     raise RuntimeError(f"Failed to bring down interface: {result.stderr}")

    def get_interface_status(self, interface_name: str) -> dict | None:
        """Get status of a WireGuard interface.

        Args:
            interface_name: Interface name.

        Returns:
            Dictionary with interface status, or None if not found.
        """
        self._check_available()

        result = subprocess.run(
            ["sudo", "wg", "show", interface_name],
            capture_output=True,
            text=True,
        )
        if result.returncode != 0:
            return None

        # Parse output
        status = {
            "interface": interface_name,
            "public_key": "",
            "listen_port": 0,
            "peers": [],
        }

        current_peer = None
        for line in result.stdout.splitlines():
            line = line.strip()
            if line.startswith("public key:"):
                if current_peer:
                    status["peers"].append(current_peer)
                    current_peer = None
                status["public_key"] = line.split(":")[-1].strip()
            elif line.startswith("listening port:"):
                status["listen_port"] = int(line.split(":")[-1].strip())
            elif line.startswith("peer:"):
                if current_peer:
                    status["peers"].append(current_peer)
                current_peer = {"public_key": line.split(":")[-1].strip()}
            elif current_peer:
                if line.startswith("endpoint:"):
                    current_peer["endpoint"] = line.split("endpoint:")[-1].strip()
                elif line.startswith("allowed ips:"):
                    current_peer["allowed_ips"] = line.split(":")[-1].strip()
                elif line.startswith("latest handshake:"):
                    current_peer["latest_handshake"] = line.split(":")[-1].strip()
                elif line.startswith("transfer:"):
                    current_peer["transfer"] = line.split(":")[-1].strip()

        if current_peer:
            status["peers"].append(current_peer)

        return status

    def is_interface_up(self, interface_name: str) -> bool:
        """Check if a WireGuard interface is up.

        Args:
            interface_name: Interface name.

        Returns:
            True if interface is up.
        """
        result = subprocess.run(
            ["ip", "link", "show", interface_name],
            capture_output=True,
            text=True,
        )
        return result.returncode == 0 and "UP" in result.stdout
