"""Fix broken Python virtual environments."""

from __future__ import annotations

import os
import shutil
import subprocess
import sys
from pathlib import Path

from xnv.diagnose import Diagnosis, Issue, diagnose


# --- Terminal colors ---
GREEN = "\033[32m"
YELLOW = "\033[33m"
RED = "\033[31m"
BLUE = "\033[34m"
BOLD = "\033[1m"
NC = "\033[0m"


def _print_issue(issue: Issue) -> None:
    if issue.severity == "error":
        print(f"  {RED}✗{NC} [{issue.code}] {issue.message}")
    elif issue.severity == "warn":
        print(f"  {YELLOW}⚠{NC} [{issue.code}] {issue.message}")
    else:
        print(f"  {GREEN}✓{NC} [{issue.code}] {issue.message}")
    if issue.fix_hint:
        print(f"      hint: {BLUE}{issue.fix_hint}{NC}")


def _run(cmd: list[str], cwd: Path | None = None) -> int:
    """Run command with live output."""
    print(f"  {BLUE}${NC} {' '.join(cmd)}")
    result = subprocess.run(cmd, cwd=cwd)
    return result.returncode


def _find_system_python() -> str:
    """Find the best system Python >= 3.10."""
    candidates = ["python3.13", "python3.12", "python3.11", "python3.10", "python3"]
    for name in candidates:
        path = shutil.which(name)
        if path:
            try:
                result = subprocess.run(
                    [path, "-c", "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"],
                    capture_output=True, text=True, timeout=5,
                )
                if result.returncode == 0:
                    major, minor = result.stdout.strip().split(".")
                    if int(major) >= 3 and int(minor) >= 10:
                        return path
            except (subprocess.TimeoutExpired, ValueError, OSError):
                continue
    return "python3"


def nuke_venv(project_dir: Path, venv_name: str = "venv") -> bool:
    """Remove virtual environment completely."""
    venv_path = project_dir / venv_name
    if venv_path.exists():
        print(f"  {YELLOW}Removing{NC} {venv_path} ...")
        shutil.rmtree(venv_path)
        print(f"  {GREEN}Removed{NC}")
        return True
    print(f"  {YELLOW}No venv at{NC} {venv_path}")
    return False


def create_venv(project_dir: Path, venv_name: str = "venv") -> bool:
    """Create a fresh virtual environment."""
    venv_path = project_dir / venv_name
    python = _find_system_python()
    print(f"  {BLUE}Creating venv with{NC} {python} ...")
    rc = _run([python, "-m", "venv", str(venv_path)], cwd=project_dir)
    if rc != 0:
        print(f"  {RED}Failed to create venv{NC}")
        return False
    # Upgrade pip
    pip_bin = str(venv_path / "bin" / "pip")
    python_bin = str(venv_path / "bin" / "python")
    if sys.platform == "win32":
        pip_bin = str(venv_path / "Scripts" / "pip.exe")
        python_bin = str(venv_path / "Scripts" / "python.exe")
    _run([python_bin, "-m", "pip", "install", "--upgrade", "pip"], cwd=project_dir)
    print(f"  {GREEN}Venv created:{NC} {venv_path}")
    return True


def fix_poetry_lock(project_dir: Path) -> bool:
    """Regenerate poetry.lock if stale."""
    print(f"  {BLUE}Regenerating poetry.lock ...{NC}")
    rc = _run(["poetry", "lock"], cwd=project_dir)
    if rc != 0:
        print(f"  {RED}poetry lock failed{NC}")
        return False
    print(f"  {GREEN}poetry.lock regenerated{NC}")
    return True


def install_deps(project_dir: Path, venv_path: Path | None = None, dev: bool = False) -> bool:
    """Install project dependencies."""
    poetry = shutil.which("poetry")
    if poetry:
        args = ["poetry", "install"]
        if not dev:
            args += ["--only", "main"]
        rc = _run(args, cwd=project_dir)
        return rc == 0

    # Fallback to pip
    if venv_path:
        pip_bin = str(venv_path / "bin" / "pip")
        if sys.platform == "win32":
            pip_bin = str(venv_path / "Scripts" / "pip.exe")
    else:
        pip_bin = "pip"

    extra = ".[dev]" if dev else "."
    rc = _run([pip_bin, "install", "-e", extra], cwd=project_dir)
    return rc == 0


def fix(
    project_dir: Path | str | None = None,
    force_recreate: bool = False,
    install: bool = True,
    dev: bool = False,
) -> bool:
    """Diagnose and fix environment issues. Returns True if environment is healthy after fix."""
    if project_dir is None:
        project_dir = Path.cwd()
    project_dir = Path(project_dir).resolve()

    print(f"\n{BOLD}xnv fix{NC} — {project_dir}\n")

    diag = diagnose(project_dir)

    # Print current state
    print(f"{BOLD}Diagnosis:{NC}")
    for issue in diag.issues:
        _print_issue(issue)
    print()

    if diag.healthy and not force_recreate:
        print(f"{GREEN}Environment looks healthy.{NC}")
        if install:
            print(f"\n{BOLD}Installing dependencies ...{NC}")
            return install_deps(project_dir, diag.venv_path, dev=dev)
        return True

    # --- Fix stale poetry.lock ---
    if diag.poetry_lock_stale:
        print(f"{BOLD}Fixing poetry.lock ...{NC}")
        fix_poetry_lock(project_dir)

    # --- Fix broken venv ---
    venv_errors = {i.code for i in diag.errors}
    venv_broken = venv_errors & {
        "VENV_NO_PYTHON",
        "VENV_BROKEN_PYTHON",
        "VENV_PYTHON_FAIL",
        "VENV_NO_PIP",
        "VENV_BROKEN_PIP",
        "VENV_PIP_FAIL",
        "VENV_PIP_SHIM_BROKEN",
        "VENV_STALE_HOME",
    }

    if venv_broken or force_recreate:
        venv_name = diag.venv_path.name if diag.venv_path else "venv"
        print(f"{BOLD}Recreating venv ...{NC}")
        nuke_venv(project_dir, venv_name)
        if not create_venv(project_dir, venv_name):
            return False
        # Update venv_path for install step
        diag.venv_path = project_dir / venv_name

    # --- Install deps ---
    if install:
        print(f"\n{BOLD}Installing dependencies ...{NC}")
        if not install_deps(project_dir, diag.venv_path, dev=dev):
            return False

    # --- Verify ---
    print(f"\n{BOLD}Verifying ...{NC}")
    diag2 = diagnose(project_dir)
    for issue in diag2.issues:
        _print_issue(issue)

    if diag2.healthy:
        print(f"\n{GREEN}{BOLD}✓ Environment fixed!{NC}")
        if diag.venv_path:
            print(f"  Activate with: {BLUE}source {diag.venv_path}/bin/activate{NC}")
    else:
        print(f"\n{RED}{BOLD}✗ Some issues remain{NC}")

    return diag2.healthy
