"""Dependency collection utilities for Mira."""

from __future__ import annotations

import importlib.metadata
import os
import shutil
import subprocess
import sys

from mira.builder.compiler import compile_file
NATIVE_EXTENSIONS: tuple[str, ...] = (".dll", ".pyd", ".so", ".dylib")
SKIP_DIRS: set[str] = {
    "__pycache__",
    ".git",
    ".venv",
    "venv",
    "bin",
    "Scripts",
}


class DependencyCollector:
    """Install and collect dependencies into build directories."""

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

        Args:
            python_executable: Path to Python executable used for pip.
        """
        self.python_executable = python_executable or sys.executable

    def install_to(self, dependencies: list[str], target_dir: str) -> None:
        """Install dependencies into a target directory using pip.

        Args:
            dependencies: List of dependency specifiers.
            target_dir: Directory to install packages into.

        Raises:
            RuntimeError: If pip fails.
        """
        if not dependencies:
            return

        os.makedirs(target_dir, exist_ok=True)
        cmd = [
            self.python_executable,
            "-m",
            "pip",
            "install",
            "--no-compile",
            "--target",
            target_dir,
            *dependencies,
        ]
        result = subprocess.run(cmd, capture_output=True, text=True)
        if result.returncode != 0:
            message = result.stderr.strip() or result.stdout.strip()
            raise RuntimeError(f"pip failed to install dependencies: {message}")

    def collect(self, target_dir: str, libs_dir: str, dlls_dir: str) -> None:
        """Collect installed dependencies into libs/ and dlls/ folders.

        Args:
            target_dir: Directory with installed dependencies.
            libs_dir: Output directory for Python libraries.
            dlls_dir: Output directory for native binaries.
        """
        if not os.path.isdir(target_dir):
            return

        os.makedirs(libs_dir, exist_ok=True)
        os.makedirs(dlls_dir, exist_ok=True)

        for root, dirs, files in os.walk(target_dir):
            dirs[:] = [
                d
                for d in dirs
                if d not in SKIP_DIRS
                and not d.endswith(".dist-info")
                and not d.endswith(".egg-info")
            ]
            for filename in files:
                src_path = os.path.join(root, filename)
                rel_path = os.path.relpath(src_path, target_dir)
                lower_name = filename.lower()

                if lower_name.endswith(NATIVE_EXTENSIONS):
                    dst_path = os.path.join(dlls_dir, rel_path)
                    os.makedirs(os.path.dirname(dst_path), exist_ok=True)
                    shutil.copy2(src_path, dst_path)
                    continue

                if lower_name.endswith(".py"):
                    rel_no_ext = os.path.splitext(rel_path)[0] + ".pyc"
                    dst_path = os.path.join(libs_dir, rel_no_ext)
                    compile_file(src_path, dst_path)
                    continue

                dst_path = os.path.join(libs_dir, rel_path)
                os.makedirs(os.path.dirname(dst_path), exist_ok=True)
                shutil.copy2(src_path, dst_path)

    def get_installed_versions(self, target_dir: str) -> dict[str, str]:
        """Read installed package versions from a target directory.

        Args:
            target_dir: Directory containing installed distributions.

        Returns:
            Dictionary of {package_name: version}.
        """
        versions: dict[str, str] = {}
        if not os.path.isdir(target_dir):
            return versions

        for dist in importlib.metadata.distributions(path=[target_dir]):
            name = dist.metadata.get("Name") or dist.name
            if not name:
                continue
            versions[name] = dist.version
        return versions
