"""Desktop integration for disposable containers.

Creates .desktop launchers that spawn fresh disposable instances.
"""

from __future__ import annotations

import os
import shlex
import shutil
import subprocess
from pathlib import Path
from typing import TYPE_CHECKING

from gaol.domains.models import DisposableConfig, DOMAIN_COLORS, SecurityDomain
from gaol.domains.store import get_domain_store

if TYPE_CHECKING:
    pass


class DisposableDesktopGenerator:
    """Generate .desktop files for disposable containers.

    Creates desktop entries that launch fresh disposable instances
    using gaol's disposable launch command.

    Example:
        >>> generator = DisposableDesktopGenerator()
        >>> config = DisposableConfig(
        ...     name="browser",
        ...     template_name="debian-browser",
        ...     application="/usr/bin/firefox",
        ...     desktop_name="Disposable Browser",
        ...     icon="firefox",
        ...     icon_color="#CC0000",
        ... )
        >>> path = generator.install(config)
        >>> print(path)
        /home/user/.local/share/applications/disp-browser.desktop
    """

    def __init__(self) -> None:
        """Initialize the generator."""
        self._store = get_domain_store()

    def get_applications_dir(self) -> Path:
        """Get the applications directory."""
        xdg_data = os.environ.get("XDG_DATA_HOME")
        if xdg_data:
            return Path(xdg_data) / "applications"
        return Path.home() / ".local" / "share" / "applications"

    def get_icons_dir(self, size: int = 128) -> Path:
        """Get the icons directory for a specific size."""
        xdg_data = os.environ.get("XDG_DATA_HOME")
        if xdg_data:
            base = Path(xdg_data)
        else:
            base = Path.home() / ".local" / "share"
        return base / "icons" / "hicolor" / f"{size}x{size}" / "apps"

    def generate_content(self, config: DisposableConfig) -> str:
        """Generate .desktop file content.

        Args:
            config: Disposable configuration.

        Returns:
            Content of the .desktop file.
        """
        name = config.desktop_name or f"Disposable {config.name.title()}"
        comment = f"Launch disposable {config.name} container"

        # Build exec command
        exec_parts = ["gaol", "disposable", "launch", config.name]
        exec_line = " ".join(shlex.quote(p) for p in exec_parts)

        # Handle file arguments for file associations
        if config.mime_types:
            exec_line += " -- %F"

        lines = [
            "[Desktop Entry]",
            "Type=Application",
            "Version=1.5",
            f"Name={self._escape_value(name)}",
            f"Exec={exec_line}",
            f"Comment={self._escape_value(comment)}",
            "Terminal=false",
            "StartupNotify=true",
        ]

        # Icon
        icon_name = self._get_icon_name(config)
        lines.append(f"Icon={icon_name}")

        # Categories
        categories = config.desktop_categories or ["Utility"]
        lines.append(f"Categories={';'.join(categories)};")

        # MIME types
        if config.mime_types:
            lines.append(f"MimeType={';'.join(config.mime_types)};")

        # Keywords
        lines.append(f"Keywords=disposable;container;ephemeral;{config.name};")

        # Gaol metadata
        lines.append(f"X-Gaol-Disposable={config.name}")
        lines.append(f"X-Gaol-Template={config.template_name}")

        return "\n".join(lines) + "\n"

    def _escape_value(self, value: str) -> str:
        """Escape special characters in .desktop values."""
        value = value.replace("\\", "\\\\")
        value = value.replace("\n", "\\n")
        value = value.replace("\t", "\\t")
        return value

    def _get_icon_name(self, config: DisposableConfig) -> str:
        """Get the icon name for a disposable."""
        if config.icon_color:
            # Use tinted icon name
            return f"disp-{config.name}"
        elif config.icon:
            return config.icon
        else:
            # Default icons by type
            defaults = {
                "browser": "web-browser",
                "file_viewer": "document-viewer",
                "terminal": "utilities-terminal",
                "custom": "application-x-executable",
            }
            return defaults.get(config.disposable_type.value, "application-x-executable")

    def _get_desktop_filename(self, config: DisposableConfig) -> str:
        """Get the .desktop filename for a disposable."""
        return f"disp-{config.name}.desktop"

    def install(self, config: DisposableConfig) -> Path:
        """Install a .desktop file for a disposable.

        Also creates a tinted icon if icon_color is specified.

        Args:
            config: Disposable configuration.

        Returns:
            Path to the installed .desktop file.
        """
        apps_dir = self.get_applications_dir()
        apps_dir.mkdir(parents=True, exist_ok=True)

        desktop_path = apps_dir / self._get_desktop_filename(config)

        # Generate and write content
        content = self.generate_content(config)
        desktop_path.write_text(content)
        desktop_path.chmod(0o755)

        # Create tinted icon if needed
        if config.icon_color and config.icon:
            self._create_tinted_icon(config)

        # Update desktop database
        self._update_desktop_database()

        return desktop_path

    def uninstall(self, name: str) -> bool:
        """Uninstall a disposable's .desktop file.

        Args:
            name: Disposable name.

        Returns:
            True if file was removed.
        """
        desktop_path = self.get_applications_dir() / f"disp-{name}.desktop"

        if desktop_path.exists():
            desktop_path.unlink()
            self._update_desktop_database()

            # Also remove icon if it exists
            self._remove_tinted_icon(name)
            return True

        return False

    def _create_tinted_icon(self, config: DisposableConfig) -> Path | None:
        """Create a tinted version of an icon.

        Args:
            config: Disposable configuration with icon and icon_color.

        Returns:
            Path to created icon, or None if creation failed.
        """
        if not config.icon or not config.icon_color:
            return None

        # Find source icon
        source_icon = self._find_icon(config.icon)
        if not source_icon:
            return None

        # Create destination path
        icons_dir = self.get_icons_dir(128)
        icons_dir.mkdir(parents=True, exist_ok=True)
        dest_icon = icons_dir / f"disp-{config.name}.png"

        # Use ImageMagick to tint
        try:
            subprocess.run(
                [
                    "magick", str(source_icon),
                    "-grayscale", "Rec709Luminance",
                    "-fill", config.icon_color,
                    "-tint", "100",
                    str(dest_icon),
                ],
                check=True,
                capture_output=True,
            )
            return dest_icon
        except (subprocess.CalledProcessError, FileNotFoundError):
            # ImageMagick not available or failed
            return None

    def _find_icon(self, icon_name: str) -> Path | None:
        """Find an icon file by name.

        Args:
            icon_name: Icon name (without extension).

        Returns:
            Path to icon file, or None if not found.
        """
        # Check if it's already a path
        if "/" in icon_name:
            path = Path(icon_name)
            if path.exists():
                return path
            return None

        # Search in standard locations
        search_paths = [
            Path("/usr/share/icons/hicolor/128x128/apps"),
            Path("/usr/share/icons/hicolor/64x64/apps"),
            Path("/usr/share/icons/hicolor/48x48/apps"),
            Path("/usr/share/icons/hicolor/scalable/apps"),
            Path("/usr/share/pixmaps"),
        ]

        for search_dir in search_paths:
            for ext in [".png", ".svg", ".xpm"]:
                icon_path = search_dir / f"{icon_name}{ext}"
                if icon_path.exists():
                    return icon_path

        return None

    def _remove_tinted_icon(self, name: str) -> None:
        """Remove a tinted icon."""
        for size in [48, 64, 128]:
            icon_path = self.get_icons_dir(size) / f"disp-{name}.png"
            if icon_path.exists():
                icon_path.unlink()

    def _update_desktop_database(self) -> None:
        """Update the desktop database after changes."""
        apps_dir = self.get_applications_dir()
        try:
            subprocess.run(
                ["update-desktop-database", str(apps_dir)],
                check=False,
                capture_output=True,
            )
        except FileNotFoundError:
            pass  # update-desktop-database not available

    def list_installed(self) -> list[str]:
        """List installed disposable desktop entries.

        Returns:
            List of disposable names with installed .desktop files.
        """
        apps_dir = self.get_applications_dir()
        installed = []

        for desktop_file in apps_dir.glob("disp-*.desktop"):
            # Extract name from filename
            name = desktop_file.stem[5:]  # Remove "disp-" prefix
            installed.append(name)

        return installed


# Convenience functions


def install_disposable_desktop(config: DisposableConfig) -> Path:
    """Install a desktop entry for a disposable.

    Args:
        config: Disposable configuration.

    Returns:
        Path to installed .desktop file.
    """
    generator = DisposableDesktopGenerator()
    return generator.install(config)


def uninstall_disposable_desktop(name: str) -> bool:
    """Uninstall a disposable's desktop entry.

    Args:
        name: Disposable name.

    Returns:
        True if file was removed.
    """
    generator = DisposableDesktopGenerator()
    return generator.uninstall(name)
