"""Wrapper script generation and installation.

This module handles creating and managing wrapper scripts in ~/.local/bin/
that transparently execute Homebrew binaries in the container.
"""

from __future__ import annotations

from datetime import datetime
from pathlib import Path

import yaml

from gaol.brew.exceptions import WrapperExistsError, WrapperNotFoundError
from gaol.brew.models import BrewConfig, InstalledPackage
from gaol.config import get_data_dir


class WrapperInstaller:
    """Install and manage wrapper scripts.

    Creates bash wrapper scripts that invoke `gaol brew run`
    to transparently execute containerized Homebrew binaries.

    Example:
        >>> installer = WrapperInstaller()
        >>> installed = installer.install("ffmpeg", ["ffmpeg", "ffprobe"])
        >>> for entry in installed:
        ...     print(entry.wrapper_path)
    """

    def __init__(self, config: BrewConfig | None = None) -> None:
        """Initialize the wrapper installer.

        Args:
            config: Optional configuration.
        """
        self.config = config or BrewConfig()
        self._store_path = get_data_dir() / "brew-entries.yaml"

    def get_wrapper_dir(self) -> Path:
        """Get wrapper script directory.

        Returns:
            Path to wrapper directory (default ~/.local/bin).
        """
        if self.config.wrapper_dir:
            return Path(self.config.wrapper_dir)
        return Path.home() / ".local" / "bin"

    def install(
        self,
        package: str,
        binaries: list[str],
        overwrite: bool = False,
    ) -> list[InstalledPackage]:
        """Install wrapper scripts for a package.

        Creates executable bash scripts that invoke gaol brew run.

        Args:
            package: Package name.
            binaries: List of binary names to create wrappers for.
            overwrite: Whether to overwrite existing wrappers.

        Returns:
            List of installed package entries.

        Raises:
            WrapperExistsError: If wrapper exists and overwrite=False.
        """
        wrapper_dir = self.get_wrapper_dir()
        wrapper_dir.mkdir(parents=True, exist_ok=True)

        installed = []
        for binary in binaries:
            wrapper_path = wrapper_dir / binary

            # Check for existing wrapper
            if wrapper_path.exists() and not overwrite:
                # Check if it's our wrapper
                content = wrapper_path.read_text()
                if "gaol brew run" not in content:
                    raise WrapperExistsError(binary, str(wrapper_path))

            # Generate wrapper script
            script = self._generate_wrapper(binary)
            wrapper_path.write_text(script)
            wrapper_path.chmod(0o755)

            entry = InstalledPackage(
                package_name=package,
                container_name=self.config.container_name,
                wrapper_path=str(wrapper_path),
                binaries=binaries,
                installed_at=datetime.now(),
            )
            installed.append(entry)

        # Save to tracking store (one entry per package, not per binary)
        if installed:
            self._save_installed(
                InstalledPackage(
                    package_name=package,
                    container_name=self.config.container_name,
                    wrapper_path=str(wrapper_dir / binaries[0]),
                    binaries=binaries,
                    installed_at=datetime.now(),
                )
            )

        return installed

    def _generate_wrapper(self, binary: str) -> str:
        """Generate wrapper script content.

        Args:
            binary: Binary name.

        Returns:
            Bash script content.
        """
        return f'''#!/bin/bash
# Gaol Homebrew wrapper for {binary}
# Auto-generated - do not edit

exec gaol brew run {binary} -- "$@"
'''

    def uninstall(self, package: str) -> int:
        """Remove wrapper scripts for a package.

        Args:
            package: Package name to uninstall wrappers for.

        Returns:
            Number of wrappers removed.

        Raises:
            WrapperNotFoundError: If no wrappers found for package.
        """
        entries = self._load_installed()
        removed = 0
        found = False

        for entry in entries:
            if entry.package_name == package:
                found = True
                # Remove all binary wrappers
                for binary in entry.binaries:
                    wrapper_path = self.get_wrapper_dir() / binary
                    if wrapper_path.exists():
                        wrapper_path.unlink()
                        removed += 1

                self._remove_installed(entry)

        if not found:
            raise WrapperNotFoundError(package)

        return removed

    def list_installed(self, package: str | None = None) -> list[InstalledPackage]:
        """List installed packages with wrappers.

        Args:
            package: Optional package name to filter by.

        Returns:
            List of installed package entries.
        """
        entries = self._load_installed()

        if package:
            entries = [e for e in entries if e.package_name == package]

        return entries

    def get_wrapper_content(self, binary: str) -> str | None:
        """Get the content of a wrapper script.

        Args:
            binary: Binary name.

        Returns:
            Wrapper script content, or None if not found.
        """
        wrapper_path = self.get_wrapper_dir() / binary
        if wrapper_path.exists():
            return wrapper_path.read_text()
        return None

    def _save_installed(self, entry: InstalledPackage) -> None:
        """Save installed entry to tracking store.

        Args:
            entry: Entry to save.
        """
        entries = self._load_installed()

        # Update or add entry
        updated = False
        for i, existing in enumerate(entries):
            if existing.package_name == entry.package_name:
                entries[i] = entry
                updated = True
                break

        if not updated:
            entries.append(entry)

        self._write_store(entries)

    def _remove_installed(self, entry: InstalledPackage) -> None:
        """Remove entry from tracking store.

        Args:
            entry: Entry to remove.
        """
        entries = self._load_installed()
        entries = [e for e in entries if e.package_name != entry.package_name]
        self._write_store(entries)

    def _load_installed(self) -> list[InstalledPackage]:
        """Load installed entries from tracking store.

        Returns:
            List of installed entries.
        """
        if not self._store_path.exists():
            return []

        try:
            with open(self._store_path) as f:
                data = yaml.safe_load(f)

            if not data or "packages" not in data:
                return []

            return [InstalledPackage.model_validate(p) for p in data["packages"]]
        except Exception:
            return []

    def _write_store(self, entries: list[InstalledPackage]) -> None:
        """Write entries to tracking store.

        Args:
            entries: List of entries to write.
        """
        self._store_path.parent.mkdir(parents=True, exist_ok=True)

        data = {"packages": [e.model_dump(mode="json") for e in entries]}
        with open(self._store_path, "w") as f:
            yaml.safe_dump(data, f, default_flow_style=False)
