"""Traefik reverse proxy management for multi-project development."""

import subprocess
from dataclasses import dataclass
from pathlib import Path

from jinja2 import Template
from rich.console import Console


@dataclass
class ProjectInfo:
    """Information about a running Prism project."""

    name: str
    services: list[str]


class ProxyManager:
    """Manage shared Traefik reverse proxy for multi-project development."""

    CONTAINER_NAME = "prism-proxy"
    NETWORK_NAME = "prism_proxy_network"
    IMAGE = "traefik:v3.0"
    WEB_PORT = 80
    DASHBOARD_PORT = 8080

    def __init__(self):
        self.console = Console()

    @staticmethod
    def is_running() -> bool:
        """Check if prism-proxy container is running."""
        try:
            result = subprocess.run(
                ["docker", "ps", "-q", "-f", f"name=^{ProxyManager.CONTAINER_NAME}$"],
                capture_output=True,
                text=True,
                timeout=5,
            )
            return bool(result.stdout.strip())
        except (FileNotFoundError, subprocess.TimeoutExpired, TimeoutError):
            return False

    def start(self) -> None:
        """Start shared reverse proxy container."""
        if self.is_running():
            self.console.print("[dim]Proxy already running[/dim]")
            return

        # Create network if needed
        self._ensure_network()

        # Ensure config file exists
        config_path = self._get_config_path()

        self.console.print("[blue]Starting reverse proxy...[/blue]")

        try:
            # Start Traefik container
            subprocess.run(
                [
                    "docker",
                    "run",
                    "-d",
                    "--name",
                    self.CONTAINER_NAME,
                    "--network",
                    self.NETWORK_NAME,
                    "-p",
                    f"{self.WEB_PORT}:80",
                    "-p",
                    f"{self.DASHBOARD_PORT}:8080",
                    "-v",
                    "/var/run/docker.sock:/var/run/docker.sock:ro",
                    "-v",
                    f"{config_path}:/etc/traefik/traefik.yml:ro",
                    "--label",
                    "traefik.enable=true",
                    "--label",
                    "traefik.http.routers.dashboard.rule=Host(`traefik.localhost`)",
                    "--label",
                    "traefik.http.routers.dashboard.service=api@internal",
                    "--restart",
                    "unless-stopped",
                    self.IMAGE,
                ],
                check=True,
                capture_output=True,
            )

            self.console.print("[green]✓ Proxy started[/green]")
            self.console.print(f"  Dashboard: http://traefik.localhost:{self.DASHBOARD_PORT}")

        except subprocess.CalledProcessError as e:
            self.console.print(f"[red]Failed to start proxy: {e.stderr.decode()}[/red]")
            raise

    def stop(self) -> None:
        """Stop and remove proxy container."""
        if not self.is_running():
            self.console.print("[dim]Proxy not running[/dim]")
            return

        self.console.print("[yellow]Stopping reverse proxy...[/yellow]")

        try:
            subprocess.run(
                ["docker", "rm", "-f", self.CONTAINER_NAME],
                check=True,
                capture_output=True,
            )
            self.console.print("[green]✓ Proxy stopped[/green]")
        except subprocess.CalledProcessError as e:
            self.console.print(f"[red]Failed to stop proxy: {e.stderr.decode()}[/red]")
            raise

    def list_projects(self) -> list[ProjectInfo]:
        """List all running Prism projects connected to the proxy."""
        try:
            # Get all containers connected to the proxy network
            result = subprocess.run(
                [
                    "docker",
                    "ps",
                    "--filter",
                    f"network={self.NETWORK_NAME}",
                    "--format",
                    "{{.Names}}",
                ],
                capture_output=True,
                text=True,
                timeout=5,
            )

            if result.returncode != 0:
                return []

            # Parse container names to extract project names
            container_names = result.stdout.strip().split("\n")
            projects: dict[str, list[str]] = {}

            for name in container_names:
                if not name or name == self.CONTAINER_NAME:
                    continue

                # Container names are typically: project-name_service-name
                parts = name.split("_")
                if len(parts) >= 2:
                    project_name = parts[0]
                    service_name = "_".join(parts[1:])

                    if project_name not in projects:
                        projects[project_name] = []
                    projects[project_name].append(service_name)

            return [
                ProjectInfo(name=name, services=services) for name, services in projects.items()
            ]

        except (FileNotFoundError, subprocess.TimeoutExpired):
            return []

    def stop_all_projects(self, remove_volumes: bool = False, verbose: bool = True) -> None:
        """Stop all Prism projects (not including the proxy itself).

        Args:
            remove_volumes: Also remove volumes when stopping projects.
            verbose: Show detailed output of what's being stopped.
        """
        projects = self.list_projects()
        stopped_containers = []

        if not projects:
            self.console.print("[yellow]No running projects on proxy network[/yellow]")
        else:
            # First, try to use docker compose down for each project
            for project in projects:
                if verbose:
                    self.console.print(f"[yellow]Stopping project: {project.name}...[/yellow]")
                    for service in project.services:
                        self.console.print(f"  [dim]• {service}[/dim]")

                # Try docker compose down first (cleaner)
                compose_cmd = [
                    "docker",
                    "compose",
                    "-p",
                    project.name,
                    "down",
                    "--remove-orphans",
                ]
                if remove_volumes:
                    compose_cmd.append("--volumes")

                try:
                    result = subprocess.run(
                        compose_cmd,
                        capture_output=True,
                        timeout=60,
                    )
                    if result.returncode == 0:
                        stopped_containers.extend([f"{project.name}_{s}" for s in project.services])
                        continue
                except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
                    pass

                # Fallback: stop containers individually
                for service in project.services:
                    container_name = f"{project.name}_{service}"
                    try:
                        subprocess.run(
                            ["docker", "stop", container_name],
                            capture_output=True,
                            timeout=30,
                        )
                        subprocess.run(
                            ["docker", "rm", "-f", container_name],
                            capture_output=True,
                            timeout=10,
                        )
                        stopped_containers.append(container_name)
                    except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
                        if verbose:
                            self.console.print(f"  [red]Could not stop: {container_name}[/red]")

        # Also look for any orphaned Prism containers not on the proxy network
        orphaned = self._find_orphaned_containers()
        if orphaned:
            if verbose:
                self.console.print()
                self.console.print(f"[yellow]Found {len(orphaned)} orphaned containers...[/yellow]")

            for container in orphaned:
                if verbose:
                    self.console.print(f"  [dim]• {container}[/dim]")
                try:
                    subprocess.run(
                        ["docker", "stop", container],
                        capture_output=True,
                        timeout=30,
                    )
                    subprocess.run(
                        ["docker", "rm", "-f", container],
                        capture_output=True,
                        timeout=10,
                    )
                    stopped_containers.append(container)
                except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
                    if verbose:
                        self.console.print(f"  [red]Could not stop: {container}[/red]")

        if stopped_containers:
            self.console.print()
            self.console.print(f"[green]✓ Stopped {len(stopped_containers)} container(s)[/green]")
        else:
            self.console.print("[dim]No containers to stop[/dim]")

    def _find_orphaned_containers(self) -> list[str]:
        """Find Prism-related containers not connected to proxy network."""
        try:
            # Find containers with Prism-related labels or naming patterns
            result = subprocess.run(
                [
                    "docker",
                    "ps",
                    "-a",  # Include stopped containers
                    "--filter",
                    "label=com.prism.project",
                    "--format",
                    "{{.Names}}",
                ],
                capture_output=True,
                text=True,
                timeout=10,
            )

            containers = []
            if result.returncode == 0 and result.stdout.strip():
                containers.extend(result.stdout.strip().split("\n"))

            # Also look for containers with prism in the name not on proxy network
            result2 = subprocess.run(
                [
                    "docker",
                    "ps",
                    "-a",
                    "--filter",
                    "name=_db_1",  # Common database container suffix
                    "--filter",
                    "name=_postgres_1",
                    "--format",
                    "{{.Names}}",
                ],
                capture_output=True,
                text=True,
                timeout=10,
            )

            if result2.returncode == 0 and result2.stdout.strip():
                for name in result2.stdout.strip().split("\n"):
                    if name and name not in containers:
                        containers.append(name)

            # Exclude the proxy container itself
            return [c for c in containers if c and c != self.CONTAINER_NAME]

        except (FileNotFoundError, subprocess.TimeoutExpired):
            return []

    @staticmethod
    def _ensure_network() -> None:
        """Create proxy network if it doesn't exist."""
        try:
            subprocess.run(
                ["docker", "network", "create", ProxyManager.NETWORK_NAME],
                capture_output=True,
                timeout=5,
            )
        except (FileNotFoundError, subprocess.TimeoutExpired):
            pass  # Network might already exist, which is fine

    @staticmethod
    def _get_config_path() -> Path:
        """Get path to Traefik config file, creating it if needed."""
        config_dir = Path.home() / ".prism" / "docker"
        config_dir.mkdir(parents=True, exist_ok=True)
        config_file = config_dir / "traefik.yml"

        if not config_file.exists():
            # Generate from template
            template_path = (
                Path(__file__).parent.parent / "templates/jinja2/docker/traefik.yml.jinja2"
            )

            if template_path.exists():
                template = Template(template_path.read_text())
                config_file.write_text(template.render())
            else:
                # Fallback: create basic config
                config_file.write_text(
                    """api:
  dashboard: true
  insecure: true

entryPoints:
  web:
    address: ":80"

providers:
  docker:
    exposedByDefault: false
    network: prism_proxy_network
    watch: true

log:
  level: INFO
"""
                )

        return config_file
