import subprocess
import sys
from packaging.version import Version
from packaging.specifiers import SpecifierSet
from packaging.requirements import Requirement
from .environment import fetch_package_metadata


def get_installed_packages() -> dict[str, Version]:
    """Return a dict of {package_name: installed_version} for the current environment."""
    try:
        result = subprocess.run(
            [sys.executable, "-m", "pip", "list", "--format=freeze"],
            capture_output=True, text=True
        )
        packages = {}
        for line in result.stdout.splitlines():
            if "==" in line:
                name, ver = line.split("==", 1)
                try:
                    packages[name.lower().strip()] = Version(ver.strip())
                except Exception:
                    continue
        return packages
    except Exception:
        return {}


def fetch_dependencies(package_name: str, version: str = None) -> list[Requirement]:
    """Fetch the dependency list for a package from PyPI metadata."""
    metadata = fetch_package_metadata(package_name, version)
    if not metadata:
        return []

    requires_dist = metadata["info"].get("requires_dist") or []
    deps = []

    for dep_str in requires_dist:
        try:
            req = Requirement(dep_str)
            # Skip extras/optional dependencies (e.g. dev, test, docs)
            if not req.marker or req.marker.evaluate({"extra": ""}):
                deps.append(req)
        except Exception:
            continue

    return deps


def get_all_versions(package_name: str) -> list[Version]:
    """Fetch all available versions of a package from PyPI."""
    metadata = fetch_package_metadata(package_name)
    if not metadata:
        return []

    versions = []
    for ver_str in metadata["releases"].keys():
        try:
            versions.append(Version(ver_str))
        except Exception:
            continue

    return sorted(versions, reverse=True)


def resolve_conflicts(package_name: str, requested_version: str = None) -> dict:
    """
    Check the full dependency tree of a package against the current environment.
    Returns a resolution plan: what to install, what to upgrade, what conflicts exist.
    """
    installed = get_installed_packages()
    deps = fetch_dependencies(package_name, requested_version)

    to_install = []
    to_upgrade = []
    conflicts = []

    for req in deps:
        name = req.name.lower()
        specifier = req.specifier  # e.g. SpecifierSet(">=1.0,<2.0")

        if name not in installed:
            # Not installed at all — find best version to install
            best = _best_version(name, specifier)
            to_install.append({"package": req.name, "version": str(best) if best else None, "reason": "not installed"})

        else:
            current = installed[name]
            if current in specifier:
                # Already installed and compatible — nothing to do
                continue
            else:
                # Installed but wrong version
                best = _best_version(name, specifier)
                if best:
                    to_upgrade.append({
                        "package": req.name,
                        "from": str(current),
                        "to": str(best),
                        "required_by": package_name,
                        "specifier": str(specifier),
                    })
                else:
                    conflicts.append({
                        "package": req.name,
                        "installed": str(current),
                        "required": str(specifier),
                        "required_by": package_name,
                        "resolvable": False,
                    })

    return {
        "package": package_name,
        "version": requested_version,
        "to_install": to_install,
        "to_upgrade": to_upgrade,
        "conflicts": conflicts,
        "clean": len(conflicts) == 0,
    }


def _best_version(package_name: str, specifier: SpecifierSet) -> Version | None:
    """Find the latest version of a package that satisfies the given specifier."""
    versions = get_all_versions(package_name)
    for v in versions:
        # Skip pre-releases unless specifier explicitly allows them
        if v.is_prerelease:
            continue
        if v in specifier:
            return v
    return None