"""Service migrator orchestration.

This module provides the main ServiceMigrator class that orchestrates
the complete workflow of analyzing and migrating a systemd service
to a container.
"""

from __future__ import annotations

import contextlib
from collections.abc import Callable
from pathlib import Path
from typing import TYPE_CHECKING

from gaol.service_migration.discovery import DependencyDiscovery
from gaol.service_migration.exceptions import (
    MigrationExecutionError,
    UnsupportedServiceError,
)
from gaol.service_migration.generator import SpecGenerator
from gaol.service_migration.models import (
    WELLKNOWN_SERVICES,
    MigrationAnalysis,
    MigrationWarning,
    MigrationWarningSeverity,
)
from gaol.service_migration.parser import SystemdUnitParser
from gaol.specs.models import ContainerSpec

if TYPE_CHECKING:
    from gaol.runtimes.base import ContainerRuntime


class ServiceMigrator:
    """Orchestrates service migration from host to container.

    This class coordinates the parsing, discovery, generation, and
    optional execution of a service migration.
    """

    def __init__(
        self,
        runtime: ContainerRuntime | None = None,
        parser: SystemdUnitParser | None = None,
        discovery: DependencyDiscovery | None = None,
        generator: SpecGenerator | None = None,
    ):
        """Initialize the migrator.

        Args:
            runtime: Container runtime for execution
            parser: Custom systemd parser
            discovery: Custom dependency discovery
            generator: Custom spec generator
        """
        self._runtime = runtime
        self._parser = parser or SystemdUnitParser()
        self._discovery = discovery or DependencyDiscovery()
        self._generator = generator or SpecGenerator()

    @property
    def runtime(self) -> ContainerRuntime | None:
        """Get the container runtime."""
        return self._runtime

    @runtime.setter
    def runtime(self, value: ContainerRuntime) -> None:
        """Set the container runtime."""
        self._runtime = value

    def analyze(self, service_name: str) -> MigrationAnalysis:
        """Analyze a service for migration.

        Parses the service file, discovers dependencies, and produces
        an analysis with recommendations and warnings.

        Args:
            service_name: Name of the systemd service

        Returns:
            MigrationAnalysis with all discovered information

        Raises:
            ServiceNotFoundError: If service cannot be found
            ServiceParseError: If service cannot be parsed
        """
        # Parse the service file
        parsed_service = self._parser.parse(service_name)

        # Discover dependencies
        dependencies, warnings = self._discovery.discover(parsed_service)

        # Build the analysis
        analysis = MigrationAnalysis(
            service=parsed_service,
            dependencies=dependencies,
            warnings=warnings,
        )

        # Determine recommendations
        self._compute_recommendations(analysis)

        # Check for migration blockers
        self._check_blockers(analysis)

        return analysis

    def _compute_recommendations(self, analysis: MigrationAnalysis) -> None:
        """Compute recommendations based on analysis.

        Args:
            analysis: Analysis to update in place
        """
        service = analysis.service
        deps = analysis.dependencies

        # Default recommendations
        analysis.recommended_runtime = "nspawn"
        analysis.recommended_distro = "trixie"

        # Check if host network is needed
        # Services that bind to low ports or need raw socket access
        if any(port < 1024 for port in deps.listening_ports):
            analysis.warnings.append(
                MigrationWarning(
                    category="privileged_port",
                    message=f"Service uses privileged port(s): {[p for p in deps.listening_ports if p < 1024]}",
                    severity=MigrationWarningSeverity.INFO,
                    suggestion="Container will need CAP_NET_BIND_SERVICE or run as root.",
                )
            )

        # Check for capabilities that need privileged mode
        privileged_caps = {"CAP_SYS_ADMIN", "CAP_NET_ADMIN", "CAP_SYS_PTRACE"}
        if any(cap in privileged_caps for cap in deps.required_capabilities):
            analysis.requires_privileged = True
            analysis.warnings.append(
                MigrationWarning(
                    category="privileged_mode",
                    message="Service requires privileged capabilities",
                    severity=MigrationWarningSeverity.WARNING,
                    suggestion="Container will run in privileged mode.",
                )
            )

        # Check for host network indicators
        if service.service.private_network:
            # If service explicitly uses PrivateNetwork=yes, it wants isolation
            pass
        elif "network" in service.name.lower() or "vpn" in service.name.lower():
            analysis.requires_host_network = True
            analysis.warnings.append(
                MigrationWarning(
                    category="host_network",
                    message="Service may require host network access",
                    severity=MigrationWarningSeverity.WARNING,
                    suggestion="Consider using network=host mode.",
                )
            )

        # Add well-known service notes if available
        wellknown = WELLKNOWN_SERVICES.get(service.name)
        if wellknown and wellknown.notes:
            analysis.warnings.append(
                MigrationWarning(
                    category="wellknown_note",
                    message=wellknown.notes,
                    severity=MigrationWarningSeverity.INFO,
                )
            )

    def _check_blockers(self, analysis: MigrationAnalysis) -> None:
        """Check for conditions that prevent migration.

        Args:
            analysis: Analysis to update in place
        """
        blockers: list[str] = []

        # System services that cannot run in containers
        unmigrateable = [
            "systemd-journald",
            "systemd-logind",
            "systemd-udevd",
            "dbus",
            "polkit",
            "accounts-daemon",
            "udisks2",
            "NetworkManager",
            "network-manager",
        ]

        if analysis.service.name in unmigrateable:
            blockers.append(f"'{analysis.service.name}' is a core system service")

        # Check for kernel module dependencies
        binary = analysis.service.get_binary_path()
        if binary in ["/sbin/modprobe", "/sbin/insmod", "/usr/sbin/modprobe"]:
            blockers.append("Service loads kernel modules")

        # Check for critical warnings
        for warning in analysis.warnings:
            if (
                warning.severity == MigrationWarningSeverity.CRITICAL
                and warning.message not in blockers
            ):
                blockers.append(warning.message)

        if blockers:
            analysis.can_migrate = False
            analysis.migration_blockers = blockers

    def generate_spec(
        self,
        analysis: MigrationAnalysis,
        container_name: str | None = None,
        runtime: str | None = None,
        distro: str | None = None,
        copy_config: bool = True,
        copy_data: bool = False,
        additional_packages: list[str] | None = None,
        additional_ports: list[int] | None = None,
    ) -> ContainerSpec:
        """Generate a container specification from analysis.

        Args:
            analysis: Migration analysis
            container_name: Override container name
            runtime: Override runtime
            distro: Override distro
            copy_config: Whether to mount config files
            copy_data: Whether to mount data directories
            additional_packages: Extra packages to install
            additional_ports: Extra ports to expose

        Returns:
            ContainerSpec ready for container creation

        Raises:
            UnsupportedServiceError: If service cannot be migrated
        """
        if not analysis.can_migrate:
            raise UnsupportedServiceError(
                analysis.service.name,
                "; ".join(analysis.migration_blockers),
            )

        return self._generator.generate(
            analysis=analysis,
            container_name=container_name,
            runtime=runtime,
            distro=distro,
            copy_config=copy_config,
            copy_data=copy_data,
            additional_packages=additional_packages,
            additional_ports=additional_ports,
        )

    def execute(
        self,
        spec: ContainerSpec,
        copy_config: bool = True,
        copy_data: bool = False,
        progress_callback: Callable[[str], None] | None = None,
    ) -> str:
        """Execute the migration and create the container.

        Args:
            spec: Container specification
            copy_config: Whether to copy config files into container
            copy_data: Whether to copy data directories into container
            progress_callback: Optional callback for progress updates

        Returns:
            Container ID of the created container

        Raises:
            MigrationExecutionError: If migration fails
            RuntimeError: If no runtime is configured
        """
        if self._runtime is None:
            raise RuntimeError("No runtime configured for migration execution")

        def report(msg: str) -> None:
            if progress_callback:
                progress_callback(msg)

        try:
            # Create the container
            report(f"Creating container '{spec.name}'...")
            config = spec.to_container_config()
            container_id = self._runtime.create(config)

            # Start the container
            report(f"Starting container '{spec.name}'...")
            self._runtime.start(container_id)

            # Copy config files if requested and not using volume mounts
            if copy_config and not spec.volumes:
                report("Copying configuration files...")
                self._copy_files_to_container(
                    container_id,
                    [v.split(":")[0] for v in spec.volumes if ":ro" in v],
                )

            # Copy data directories if requested
            if copy_data:
                report("Copying data directories...")
                self._copy_files_to_container(
                    container_id,
                    [v.split(":")[0] for v in spec.volumes if ":ro" not in v and v.count(":") >= 1],
                )

            report(f"Migration complete. Container ID: {container_id}")
            return container_id

        except Exception as e:
            raise MigrationExecutionError("container_creation", str(e)) from e

    def _copy_files_to_container(self, container_id: str, paths: list[str]) -> None:
        """Copy files/directories to a container.

        Args:
            container_id: Target container ID
            paths: List of paths to copy
        """
        if self._runtime is None:
            return

        for path in paths:
            if Path(path).exists():
                # Best effort - volume mounts are preferred
                with contextlib.suppress(Exception):
                    self._runtime.copy_to(container_id, path, path)

    def write_spec(
        self,
        spec: ContainerSpec,
        output_path: str | Path,
        format: str = "yaml",
    ) -> Path:
        """Write a container specification to a file.

        Args:
            spec: Container specification
            output_path: Path to write to
            format: Output format (yaml or toml)

        Returns:
            Path to the written file
        """
        output_path = Path(output_path)

        if format == "yaml":
            content = self._generator.to_yaml(spec)
        elif format == "toml":
            content = self._generator.to_toml(spec)
        else:
            raise ValueError(f"Unsupported format: {format}")

        output_path.write_text(content)
        return output_path

    def get_analysis_summary(self, analysis: MigrationAnalysis) -> str:
        """Generate a human-readable summary of the analysis.

        Args:
            analysis: Migration analysis

        Returns:
            Formatted summary string
        """
        lines = []
        service = analysis.service
        deps = analysis.dependencies

        lines.append(f"Service: {service.name}")
        lines.append(f"File: {service.file_path}")

        if service.unit.description:
            lines.append(f"Description: {service.unit.description}")

        lines.append("")

        # Command
        binary = service.get_binary_path()
        if binary:
            lines.append(f"Binary: {binary}")
        if service.service.exec_start:
            lines.append(f"Command: {service.service.exec_start}")

        lines.append("")

        # Packages
        if deps.packages:
            lines.append("Packages:")
            for pkg in deps.packages:
                lines.append(f"  - {pkg}")

        # Config
        if deps.config_files or deps.config_directories:
            lines.append("Configuration:")
            for cf in deps.config_files:
                lines.append(f"  - {cf}")
            for cd in deps.config_directories:
                lines.append(f"  - {cd}/")

        # State/Data
        if deps.state_directories or deps.data_directories:
            lines.append("Data/State:")
            for sd in deps.state_directories:
                lines.append(f"  - {sd}")
            for dd in deps.data_directories:
                lines.append(f"  - {dd}")

        # Ports
        if deps.listening_ports:
            lines.append(f"Ports: {', '.join(str(p) for p in deps.listening_ports)}")

        # Related units
        if deps.socket_units:
            lines.append(f"Socket units: {', '.join(deps.socket_units)}")
        if deps.timer_units:
            lines.append(f"Timer units: {', '.join(deps.timer_units)}")

        lines.append("")

        # Recommendations
        lines.append("Recommendations:")
        lines.append(f"  Runtime: {analysis.recommended_runtime}")
        lines.append(f"  Distro: {analysis.recommended_distro}")
        if analysis.requires_privileged:
            lines.append("  Mode: privileged")
        if analysis.requires_host_network:
            lines.append("  Network: host")

        # Warnings
        if analysis.warnings:
            lines.append("")
            lines.append("Warnings:")
            for warning in analysis.warnings:
                prefix = {
                    MigrationWarningSeverity.INFO: "INFO",
                    MigrationWarningSeverity.WARNING: "WARN",
                    MigrationWarningSeverity.CRITICAL: "CRITICAL",
                }[warning.severity]
                lines.append(f"  [{prefix}] {warning.category}: {warning.message}")
                if warning.suggestion:
                    lines.append(f"          → {warning.suggestion}")

        # Migration status
        lines.append("")
        if analysis.can_migrate:
            lines.append("Status: ✓ Can be migrated")
        else:
            lines.append("Status: ✗ Cannot be migrated")
            for blocker in analysis.migration_blockers:
                lines.append(f"  - {blocker}")

        return "\n".join(lines)
