"""Falco installation and configuration management."""

from __future__ import annotations

import os
import shutil
import subprocess
from enum import Enum
from pathlib import Path

import yaml

from gaol.security.exceptions import (
    FalcoInstallError,
    UnsupportedDistroError,
)
from gaol.security.models import FalcoStatus

# Falco repository URLs
FALCO_GPG_KEY_URL = "https://falco.org/repo/falcosecurity-packages.asc"
FALCO_APT_REPO = "https://download.falco.org/packages/deb"
FALCO_RPM_REPO = "https://download.falco.org/packages/rpm"

# Configuration paths
FALCO_CONFIG_PATH = Path("/etc/falco/falco.yaml")
FALCO_RULES_DIR = Path("/etc/falco/rules.d")


class PackageManager(str, Enum):
    """Supported package managers."""

    APT = "apt"
    DNF = "dnf"
    YUM = "yum"
    ZYPPER = "zypper"
    PACMAN = "pacman"


class FalcoInstaller:
    """Handles Falco installation and configuration.

    This class provides methods to install Falco on various Linux distributions,
    configure gRPC output, and manage the Falco systemd service.

    Example:
        installer = FalcoInstaller()
        if not installer.is_falco_installed():
            installer.install(enable_grpc=True)
            installer.enable_service()
    """

    def __init__(self) -> None:
        """Initialize installer."""
        self._pkg_manager: PackageManager | None = None

    def detect_package_manager(self) -> PackageManager:
        """Detect the system's package manager.

        Returns:
            PackageManager enum value

        Raises:
            UnsupportedDistroError: If no supported package manager found
        """
        if self._pkg_manager:
            return self._pkg_manager

        checks = [
            ("apt-get", PackageManager.APT),
            ("dnf", PackageManager.DNF),
            ("yum", PackageManager.YUM),
            ("zypper", PackageManager.ZYPPER),
            ("pacman", PackageManager.PACMAN),
        ]

        for cmd, pkg_manager in checks:
            if shutil.which(cmd):
                self._pkg_manager = pkg_manager
                return pkg_manager

        raise UnsupportedDistroError("unknown")

    def is_falco_installed(self) -> bool:
        """Check if Falco is installed.

        Returns:
            True if Falco is installed
        """
        return shutil.which("falco") is not None

    def get_installed_version(self) -> str | None:
        """Get installed Falco version.

        Returns:
            Version string or None if not installed
        """
        if not self.is_falco_installed():
            return None

        try:
            result = subprocess.run(
                ["falco", "--version"],
                capture_output=True,
                text=True,
                timeout=10,
            )
            if result.returncode == 0:
                for line in result.stdout.split("\n"):
                    if line.startswith("Falco"):
                        parts = line.split()
                        if len(parts) >= 2:
                            return parts[1]
        except (subprocess.TimeoutExpired, FileNotFoundError):
            pass

        return None

    def install(
        self,
        version: str | None = None,
        enable_grpc: bool = True,
        grpc_socket_path: str = "/run/falco/falco.sock",
        driver_type: str = "modern_ebpf",
    ) -> None:
        """Install Falco.

        Args:
            version: Specific version to install (latest if None)
            enable_grpc: Enable gRPC output
            grpc_socket_path: Path for gRPC Unix socket
            driver_type: Driver type (modern_ebpf, ebpf, kmod)

        Raises:
            FalcoInstallError: If installation fails
            UnsupportedDistroError: If distro not supported
        """
        self._check_root()
        pkg_manager = self.detect_package_manager()

        if pkg_manager == PackageManager.APT:
            self._install_apt(version)
        elif pkg_manager == PackageManager.DNF:
            self._install_dnf(version)
        elif pkg_manager == PackageManager.YUM:
            self._install_yum(version)
        elif pkg_manager == PackageManager.ZYPPER:
            self._install_zypper(version)
        elif pkg_manager == PackageManager.PACMAN:
            self._install_pacman(version)
        else:
            raise UnsupportedDistroError(str(pkg_manager))

        # Configure after installation
        if enable_grpc:
            self.configure_grpc(socket_path=grpc_socket_path)

        # Configure driver
        self._configure_driver(driver_type)

    def _check_root(self) -> None:
        """Check if running as root."""
        if os.geteuid() != 0:
            raise FalcoInstallError(
                "Falco installation requires root privileges. "
                "Run with sudo or as root."
            )

    def _install_apt(self, version: str | None) -> None:
        """Install using apt (Debian/Ubuntu)."""
        try:
            # Add GPG key
            self._run_command(
                ["curl", "-fsSL", FALCO_GPG_KEY_URL, "-o", "/tmp/falco.gpg"]
            )
            self._run_command(
                [
                    "gpg",
                    "--dearmor",
                    "-o",
                    "/usr/share/keyrings/falco-archive-keyring.gpg",
                    "/tmp/falco.gpg",
                ]
            )

            # Add repository (using stable repo which works across codenames)
            repo_line = (
                f"deb [signed-by=/usr/share/keyrings/falco-archive-keyring.gpg] "
                f"{FALCO_APT_REPO} stable main"
            )
            repo_file = Path("/etc/apt/sources.list.d/falcosecurity.list")
            repo_file.write_text(repo_line + "\n")

            # Update and install
            self._run_command(["apt-get", "update"])

            install_cmd = ["apt-get", "install", "-y", "falco"]
            if version:
                install_cmd[-1] = f"falco={version}"
            self._run_command(install_cmd)

        except subprocess.CalledProcessError as e:
            raise FalcoInstallError(f"apt installation failed: {e}") from e

    def _install_dnf(self, version: str | None) -> None:
        """Install using dnf (Fedora/RHEL 8+)."""
        try:
            # Add repository
            self._run_command(
                [
                    "dnf",
                    "config-manager",
                    "--add-repo",
                    f"{FALCO_RPM_REPO}/falco.repo",
                ]
            )

            # Import GPG key
            self._run_command(
                ["rpm", "--import", FALCO_GPG_KEY_URL]
            )

            # Install
            install_cmd = ["dnf", "install", "-y", "falco"]
            if version:
                install_cmd[-1] = f"falco-{version}"
            self._run_command(install_cmd)

        except subprocess.CalledProcessError as e:
            raise FalcoInstallError(f"dnf installation failed: {e}") from e

    def _install_yum(self, version: str | None) -> None:
        """Install using yum (RHEL 7/CentOS)."""
        try:
            # Add repository
            repo_content = f"""[falcosecurity]
name=Falco Security Repository
baseurl={FALCO_RPM_REPO}
enabled=1
gpgcheck=1
gpgkey={FALCO_GPG_KEY_URL}
"""
            repo_file = Path("/etc/yum.repos.d/falcosecurity.repo")
            repo_file.write_text(repo_content)

            # Install
            install_cmd = ["yum", "install", "-y", "falco"]
            if version:
                install_cmd[-1] = f"falco-{version}"
            self._run_command(install_cmd)

        except subprocess.CalledProcessError as e:
            raise FalcoInstallError(f"yum installation failed: {e}") from e

    def _install_zypper(self, version: str | None) -> None:
        """Install using zypper (openSUSE)."""
        try:
            # Add repository
            self._run_command(
                ["zypper", "addrepo", "-f", FALCO_RPM_REPO, "falcosecurity"]
            )

            # Import GPG key
            self._run_command(["rpm", "--import", FALCO_GPG_KEY_URL])

            # Install
            install_cmd = ["zypper", "install", "-y", "falco"]
            if version:
                install_cmd[-1] = f"falco-{version}"
            self._run_command(install_cmd)

        except subprocess.CalledProcessError as e:
            raise FalcoInstallError(f"zypper installation failed: {e}") from e

    def _install_pacman(self, _version: str | None) -> None:
        """Install using pacman (Arch Linux).

        Args:
            _version: Version to install (unused - AUR install not supported)
        """
        # Falco is available in AUR but automated installation is not supported
        raise UnsupportedDistroError(
            "Arch Linux - please install falco from AUR manually"
        )

    def _get_debian_codename(self) -> str:
        """Get Debian/Ubuntu codename."""
        try:
            result = subprocess.run(
                ["lsb_release", "-cs"],
                capture_output=True,
                text=True,
                timeout=5,
            )
            if result.returncode == 0:
                return result.stdout.strip()
        except (subprocess.TimeoutExpired, FileNotFoundError):
            pass

        # Fallback to reading os-release
        try:
            with open("/etc/os-release") as f:
                for line in f:
                    if line.startswith("VERSION_CODENAME="):
                        return line.split("=")[1].strip().strip('"')
        except FileNotFoundError:
            pass

        return "stable"

    def _run_command(self, cmd: list[str], check: bool = True) -> subprocess.CompletedProcess[str]:
        """Run a command with error handling."""
        return subprocess.run(
            cmd,
            check=check,
            capture_output=True,
            text=True,
            timeout=300,
        )

    def configure_grpc(
        self,
        socket_path: str | None = None,
        tcp_enabled: bool = False,
        tcp_address: str = "0.0.0.0:5060",
        _tls_enabled: bool = False,
    ) -> None:
        """Configure Falco gRPC output.

        Args:
            socket_path: Unix socket path (default: /run/falco/falco.sock)
            tcp_enabled: Enable TCP listener
            tcp_address: TCP listen address
            _tls_enabled: Enable TLS for TCP (not yet implemented)
        """
        if not FALCO_CONFIG_PATH.exists():
            raise FalcoInstallError(
                f"Falco config not found at {FALCO_CONFIG_PATH}. Is Falco installed?"
            )

        # Read existing config
        with open(FALCO_CONFIG_PATH) as f:
            config = yaml.safe_load(f) or {}

        # Configure gRPC
        config["grpc"] = {
            "enabled": True,
            "threadiness": 0,  # Use auto-detection
        }

        if socket_path:
            config["grpc"]["bind_address"] = f"unix://{socket_path}"
            # Ensure socket directory exists
            socket_dir = Path(socket_path).parent
            socket_dir.mkdir(parents=True, exist_ok=True)
        elif tcp_enabled:
            config["grpc"]["bind_address"] = tcp_address

        # Enable gRPC output
        config["grpc_output"] = {
            "enabled": True,
        }

        # Enable JSON output for easier parsing
        config["json_output"] = True
        config["json_include_output_property"] = True
        config["json_include_tags_property"] = True

        # Write config back
        with open(FALCO_CONFIG_PATH, "w") as f:
            yaml.dump(config, f, default_flow_style=False)

    def configure_outputs(
        self,
        stdout: bool = True,
        syslog: bool = False,
        json_output: bool = True,
        grpc_output: bool = True,
        file_output: str | None = None,
    ) -> None:
        """Configure Falco output channels.

        Args:
            stdout: Enable stdout output
            syslog: Enable syslog output
            json_output: Enable JSON formatting
            grpc_output: Enable gRPC streaming
            file_output: Path for file output (None to disable)
        """
        if not FALCO_CONFIG_PATH.exists():
            raise FalcoInstallError("Falco config not found")

        with open(FALCO_CONFIG_PATH) as f:
            config = yaml.safe_load(f) or {}

        config["stdout_output"] = {"enabled": stdout}
        config["syslog_output"] = {"enabled": syslog}
        config["json_output"] = json_output

        if file_output:
            config["file_output"] = {
                "enabled": True,
                "keep_alive": True,
                "filename": file_output,
            }

        if grpc_output:
            config["grpc_output"] = {"enabled": True}

        with open(FALCO_CONFIG_PATH, "w") as f:
            yaml.dump(config, f, default_flow_style=False)

    def _configure_driver(self, driver_type: str) -> None:
        """Configure Falco driver type.

        Args:
            driver_type: Driver type (modern_ebpf, ebpf, kmod)
        """
        if not FALCO_CONFIG_PATH.exists():
            return

        # Create systemd override for driver selection
        override_dir = Path("/etc/systemd/system/falco.service.d")
        override_dir.mkdir(parents=True, exist_ok=True)

        # The driver is typically selected via command line or config
        # For modern systems, modern_ebpf is preferred
        with open(FALCO_CONFIG_PATH) as f:
            config = yaml.safe_load(f) or {}

        # Set engine kind based on driver type
        if driver_type == "modern_ebpf":
            config["engine"] = {"kind": "modern_ebpf"}
        elif driver_type == "ebpf":
            config["engine"] = {"kind": "ebpf", "ebpf": {"probe": ""}}
        elif driver_type == "kmod":
            config["engine"] = {"kind": "kmod"}

        with open(FALCO_CONFIG_PATH, "w") as f:
            yaml.dump(config, f, default_flow_style=False)

    def enable_service(self) -> None:
        """Enable and start Falco systemd service."""
        try:
            self._run_command(["systemctl", "daemon-reload"])
            self._run_command(["systemctl", "enable", "falco"])
            self._run_command(["systemctl", "start", "falco"])
        except subprocess.CalledProcessError as e:
            raise FalcoInstallError(f"Failed to enable Falco service: {e}") from e

    def disable_service(self) -> None:
        """Disable and stop Falco systemd service."""
        try:
            self._run_command(["systemctl", "stop", "falco"], check=False)
            self._run_command(["systemctl", "disable", "falco"], check=False)
        except subprocess.CalledProcessError:
            pass

    def restart_service(self) -> None:
        """Restart Falco service."""
        try:
            self._run_command(["systemctl", "restart", "falco"])
        except subprocess.CalledProcessError as e:
            raise FalcoInstallError(f"Failed to restart Falco service: {e}") from e

    def is_service_running(self) -> bool:
        """Check if Falco service is running."""
        try:
            result = self._run_command(
                ["systemctl", "is-active", "falco"],
                check=False,
            )
            return result.stdout.strip() == "active"
        except (subprocess.TimeoutExpired, FileNotFoundError):
            return False

    def get_status(self) -> FalcoStatus:
        """Get current Falco status.

        Returns:
            FalcoStatus with installation and runtime info
        """
        status = FalcoStatus()
        status.installed = self.is_falco_installed()

        if status.installed:
            status.version = self.get_installed_version()
            status.running = self.is_service_running()

            # Check gRPC socket
            socket_path = Path("/run/falco/falco.sock")
            if socket_path.exists():
                status.grpc_enabled = True
                status.grpc_socket = str(socket_path)

            # Check driver type from config
            if FALCO_CONFIG_PATH.exists():
                try:
                    with open(FALCO_CONFIG_PATH) as f:
                        config = yaml.safe_load(f) or {}
                    engine = config.get("engine", {})
                    status.driver_type = engine.get("kind", "unknown")
                except Exception:
                    pass

        return status

    def uninstall(self, remove_config: bool = False) -> None:
        """Uninstall Falco.

        Args:
            remove_config: Also remove configuration files
        """
        self._check_root()

        # Stop service first
        self.disable_service()

        pkg_manager = self.detect_package_manager()

        try:
            if pkg_manager == PackageManager.APT:
                cmd = ["apt-get", "remove", "-y"]
                if remove_config:
                    cmd[1] = "purge"
                cmd.append("falco")
                self._run_command(cmd)
            elif pkg_manager in (PackageManager.DNF, PackageManager.YUM):
                self._run_command(["dnf" if pkg_manager == PackageManager.DNF else "yum",
                                   "remove", "-y", "falco"])
            elif pkg_manager == PackageManager.ZYPPER:
                self._run_command(["zypper", "remove", "-y", "falco"])
        except subprocess.CalledProcessError as e:
            raise FalcoInstallError(f"Failed to uninstall Falco: {e}") from e

        if remove_config:
            # Remove config directories
            for path in [FALCO_CONFIG_PATH.parent, FALCO_RULES_DIR]:
                if path.exists():
                    shutil.rmtree(path, ignore_errors=True)


# Global installer instance
_installer: FalcoInstaller | None = None


def get_falco_installer() -> FalcoInstaller:
    """Get the global Falco installer instance."""
    global _installer
    if _installer is None:
        _installer = FalcoInstaller()
    return _installer
