"""Profile applicator for applying profiles to containers."""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING

from gaol.models.profile import Profile
from gaol.profiles.loader import get_profile_loader

if TYPE_CHECKING:
    from gaol.models.container import ContainerConfig


@dataclass
class MergedProfile:
    """Merged configuration from multiple profiles."""

    profiles: list[str] = field(default_factory=list)
    packages: list[str] = field(default_factory=list)
    package_repos: list[str] = field(default_factory=list)
    services: list[str] = field(default_factory=list)
    setup_commands: list[str] = field(default_factory=list)
    files: dict[str, str] = field(default_factory=dict)
    environment: dict[str, str] = field(default_factory=dict)
    exposed_ports: list[int] = field(default_factory=list)
    volumes: dict[str, str] = field(default_factory=dict)
    capabilities_add: list[str] = field(default_factory=list)
    capabilities_drop: list[str] = field(default_factory=list)
    requires_privileged: bool = False

    def merge_profile(self, profile: Profile) -> None:
        """Merge a profile into this merged configuration."""
        self.profiles.append(profile.name)

        # Merge packages (deduplicate)
        for pkg in profile.packages:
            if pkg not in self.packages:
                self.packages.append(pkg)

        # Merge package repos (deduplicate)
        for repo in profile.package_repos:
            if repo not in self.package_repos:
                self.package_repos.append(repo)

        # Merge services (deduplicate)
        for svc in profile.services:
            if svc not in self.services:
                self.services.append(svc)

        # Append setup commands (order matters)
        self.setup_commands.extend(profile.setup_commands)

        # Merge files (later profiles override earlier ones)
        self.files.update(profile.files)

        # Merge environment (later profiles override earlier ones)
        self.environment.update(profile.environment)

        # Merge exposed ports (deduplicate)
        for port in profile.exposed_ports:
            if port not in self.exposed_ports:
                self.exposed_ports.append(port)

        # Merge volumes (later profiles override earlier ones)
        self.volumes.update(profile.volumes)

        # Merge capabilities (deduplicate)
        for cap in profile.capabilities_add:
            if cap not in self.capabilities_add:
                self.capabilities_add.append(cap)
        for cap in profile.capabilities_drop:
            if cap not in self.capabilities_drop:
                self.capabilities_drop.append(cap)

        # Set privileged if any profile requires it
        if profile.requires_privileged:
            self.requires_privileged = True

    def generate_setup_script(self, distro: str = "trixie") -> str:
        """Generate a shell script to set up the container.

        Args:
            distro: The distribution name for package manager selection

        Returns:
            Shell script content
        """
        from gaol.distro import (
            PackageManager,
            get_distro_info,
            get_package_manager_commands,
            get_service_commands,
            translate_packages,
        )

        lines = ["#!/bin/bash", "set -e", ""]

        # Get distro info and package manager commands
        try:
            distro_info = get_distro_info(distro)
            pkg_commands = get_package_manager_commands(distro)
        except KeyError:
            # Fall back to apt for unknown distros
            from gaol.distro import DistroFamily, ServiceManager

            distro_info = type(
                "DistroInfo",
                (),
                {
                    "package_manager": PackageManager.APT,
                    "service_manager": ServiceManager.SYSTEMD,
                    "family": DistroFamily.DEBIAN,
                },
            )()
            pkg_commands = {
                "update": "apt-get update",
                "install": "apt-get install -y",
                "clean": "apt-get clean",
            }

        pkg_install = pkg_commands["install"]
        pkg_update = pkg_commands["update"]

        # Run setup commands FIRST (for DNS fixes, user creation, etc.)
        if self.setup_commands:
            lines.append("# Run setup commands (before package installation)")
            for cmd in self.setup_commands:
                lines.append(cmd)
            lines.append("")

        # Add package repositories
        if self.package_repos:
            lines.append("# Add package repositories")
            for repo in self.package_repos:
                if distro_info.package_manager == PackageManager.APT:
                    lines.append(f"echo '{repo}' >> /etc/apt/sources.list.d/gaol.list")
                elif distro_info.package_manager == PackageManager.DNF:
                    lines.append(f"dnf config-manager --add-repo {repo}")
                elif distro_info.package_manager == PackageManager.PACMAN:
                    lines.append(f"echo '{repo}' >> /etc/pacman.conf")
                # APK repos are handled differently (via /etc/apk/repositories)
            lines.append("")

        # Update package lists
        lines.append("# Update package lists")
        lines.append(pkg_update)
        lines.append("")

        # Translate and install packages
        if self.packages:
            translated_packages = translate_packages(self.packages, distro)
            if translated_packages:
                lines.append("# Install packages")
                lines.append(f"{pkg_install} {' '.join(translated_packages)}")
                lines.append("")

        # Create files
        if self.files:
            lines.append("# Create configuration files")
            for path, content in self.files.items():
                lines.append(f"mkdir -p $(dirname '{path}')")
                lines.append(f"cat > '{path}' << 'GAOL_EOF'")
                lines.append(content)
                lines.append("GAOL_EOF")
                lines.append("")

        # Enable and start services
        if self.services:
            lines.append("# Enable and start services")
            for svc in self.services:
                enable_cmd = get_service_commands(distro, svc, "enable")
                start_cmd = get_service_commands(distro, svc, "start")
                lines.append(f"{enable_cmd} || true")
                lines.append(f"{start_cmd} || true")
            lines.append("")

        lines.append("echo 'Profile setup complete!'")
        return "\n".join(lines)


class ProfileApplicator:
    """Applies profiles to container configurations."""

    def __init__(self) -> None:
        """Initialize the profile applicator."""
        self._loader = get_profile_loader()

    def resolve_and_merge(self, profile_names: list[str]) -> MergedProfile:
        """Resolve profile dependencies and merge into single configuration.

        Args:
            profile_names: List of profile names to apply

        Returns:
            MergedProfile with all settings combined
        """
        merged = MergedProfile()

        if not profile_names:
            return merged

        # Resolve dependencies to get ordered list
        profiles = self._loader.resolve_dependencies(profile_names)

        # Merge each profile in order
        for profile in profiles:
            merged.merge_profile(profile)

        return merged

    def apply_to_config(
        self, config: ContainerConfig, merged: MergedProfile
    ) -> ContainerConfig:
        """Apply merged profile settings to a container config.

        Args:
            config: Original container configuration
            merged: Merged profile settings

        Returns:
            Updated container configuration
        """
        # Create a copy of the config with updated values
        updates: dict = {}

        # Merge environment variables (profile defaults, then config overrides)
        env = dict(merged.environment)
        env.update(config.environment)
        updates["environment"] = env

        # Merge volumes (profile defaults, then config overrides)
        volumes = dict(merged.volumes)
        volumes.update(config.volumes)
        updates["volumes"] = volumes

        # Merge port mappings - add exposed ports from profiles
        # Only add profile ports if not already mapped
        existing_container_ports = set(config.network.ports.values())
        network_ports = dict(config.network.ports)
        for port in merged.exposed_ports:
            if port not in existing_container_ports and port not in network_ports:
                # Auto-assign same host port (user can override)
                network_ports[port] = port

        # Set privileged if required by any profile
        if merged.requires_privileged and not config.privileged:
            updates["privileged"] = True

        # Store profile names for reference
        updates["profiles"] = merged.profiles

        # Create updated network config
        from gaol.models.container import NetworkConfig

        updates["network"] = NetworkConfig(
            mode=config.network.mode,
            ports=network_ports,
            dns=config.network.dns,
            hostname=config.network.hostname,
        )

        # Return updated config
        return config.model_copy(update=updates)


def get_profile_applicator() -> ProfileApplicator:
    """Get a profile applicator instance."""
    return ProfileApplicator()
