"""Local repository management for Mira packages."""

from __future__ import annotations

import os
import shutil
from typing import Any

from mira.library.resolver import PackageResolver


class MiraRepository:
    """Manage a local repository of .mira packages."""

    def __init__(self, base_dir: str | None = None) -> None:
        """Create a repository.

        Args:
            base_dir: Optional base directory. Defaults to ~/.mira/packages/.
        """
        if base_dir is None:
            home_override = os.environ.get("MIRA_HOME")
            if home_override:
                base_dir = os.path.join(home_override, "packages")
            else:
                base_dir = os.path.join(os.path.expanduser("~"), ".mira", "packages")
        self.base_dir = base_dir
        os.makedirs(self.base_dir, exist_ok=True)

    def install(self, mira_path: str) -> str:
        """Install a .mira package into the repository.

        Args:
            mira_path: Path to the .mira file.

        Returns:
            Destination path in the repository.
        """
        if not os.path.isfile(mira_path):
            raise RuntimeError(f".mira file not found: {mira_path}")
        filename = os.path.basename(mira_path)
        dest = os.path.join(self.base_dir, filename)
        shutil.copy2(mira_path, dest)
        return dest

    def uninstall(self, name: str, key: str | None = None) -> list[str]:
        """Remove packages by name from the repository.

        Args:
            name: Package name.
            key: Optional encryption key for protected archives.

        Returns:
            List of removed package paths.
        """
        removed: list[str] = []
        for path, manifest in self._iter_packages(key=key):
            if manifest.get("name") == name:
                try:
                    os.remove(path)
                    removed.append(path)
                except OSError:
                    continue
        if not removed:
            raise RuntimeError(f"Package not found: {name}")
        return removed

    def list_packages(self, key: str | None = None) -> list[dict[str, Any]]:
        """List installed packages with their manifests.

        Args:
            key: Optional encryption key for protected archives.
        """
        packages: list[dict[str, Any]] = []
        for _, manifest in self._iter_packages(key=key):
            packages.append(manifest)
        return packages

    def get_package_path(self, name: str, key: str | None = None) -> str:
        """Get a package path by name.

        Args:
            name: Package name.
            key: Optional encryption key for protected archives.

        Raises:
            RuntimeError: If not found.
        """
        for path, manifest in self._iter_packages(key=key):
            if manifest.get("name") == name:
                return path
        raise RuntimeError(f"Package not found: {name}")

    def find(
        self, name: str, version: str | None = None, key: str | None = None
    ) -> str:
        """Find a package by name and optional version."""
        for path, manifest in self._iter_packages(key=key):
            if manifest.get("name") != name:
                continue
            if version is None or manifest.get("version") == version:
                return path
        raise RuntimeError(f"Package not found: {name}")

    def _iter_packages(self, key: str | None = None):
        """Yield (path, manifest) for all .mira packages in the repository."""
        resolver = PackageResolver()
        for filename in os.listdir(self.base_dir):
            if not filename.endswith(".mira"):
                continue
            path = os.path.join(self.base_dir, filename)
            try:
                manifest = resolver.read_manifest(path, key=key)
            except Exception:
                continue
            yield path, manifest
