"""Quality gate commands — check-size, check-coverage, check-deps."""

from __future__ import annotations

import json
import re
import subprocess
from pathlib import Path

from afd import CommandResult, error, success

from botcore.config import load_config
from botcore.utils.runner import run_python_module
from botcore.utils.workspace import find_workspace


def _parse_version(version: str) -> tuple[int, int, int]:
    """Parse version string into (major, minor, patch) tuple."""
    match = re.match(r"(\d+)(?:\.(\d+))?(?:\.(\d+))?", version)
    if not match:
        return (0, 0, 0)
    major = int(match.group(1)) if match.group(1) else 0
    minor = int(match.group(2)) if match.group(2) else 0
    patch = int(match.group(3)) if match.group(3) else 0
    return (major, minor, patch)


async def dev_check_size(
    path: str | None = None,
    warn_threshold: int | None = None,
    error_threshold: int | None = None,
) -> CommandResult[dict]:
    """Check Python file sizes for agent-friendly limits.

    Uses warn/error thresholds from config if not explicitly provided.
    """
    ws = find_workspace()
    config = load_config(workspace=ws)
    warn_threshold = warn_threshold if warn_threshold is not None else config.file_size_warn
    error_threshold = error_threshold if error_threshold is not None else config.file_size_error

    check_path = Path(path) if path else (ws / "src" if ws else Path("src"))

    if not check_path.exists():
        return error(
            "PATH_NOT_FOUND",
            f"Path not found: {check_path}",
            suggestion="Verify the path exists or omit to use default src/",
        )

    results: dict = {"files_checked": 0, "warnings": [], "errors": [], "ok": []}

    for py_file in check_path.rglob("*.py"):
        if "__pycache__" in str(py_file) or "test_" in py_file.name:
            continue

        try:
            lines = len(py_file.read_text(encoding="utf-8").splitlines())
        except Exception:
            continue

        results["files_checked"] += 1
        relative_path = str(py_file.relative_to(ws) if ws else py_file)

        if lines >= error_threshold:
            results["errors"].append({"file": relative_path, "lines": lines})
        elif lines >= warn_threshold:
            results["warnings"].append({"file": relative_path, "lines": lines})
        else:
            results["ok"].append({"file": relative_path, "lines": lines})

    results["errors"].sort(key=lambda x: x["lines"], reverse=True)
    results["warnings"].sort(key=lambda x: x["lines"], reverse=True)

    has_errors = len(results["errors"]) > 0

    if has_errors:
        file_list = ", ".join(f"{e['file']} ({e['lines']})" for e in results["errors"])
        return error(
            "FILES_TOO_LARGE",
            f"Files exceeding {error_threshold} lines: {file_list}",
            suggestion="Refactor large files into smaller modules",
        )

    return success(
        data=results,
        reasoning=f"Checked {results['files_checked']} files, {len(results['warnings'])} warnings",
    )


async def dev_check_coverage() -> CommandResult[dict]:
    """Check test coverage against configured thresholds."""
    ws = find_workspace()
    if not ws:
        return error(
            "NO_WORKSPACE",
            "Could not find workspace root",
            suggestion="Run from within a Git repository",
        )

    config = load_config(workspace=ws)
    threshold = config.coverage_threshold
    warn_threshold = config.coverage_warn_threshold
    coverage_paths = config.coverage_paths

    cov_args = [f"--cov={p}" for p in coverage_paths]

    await run_python_module(
        "pytest",
        [*cov_args, "--cov-report=json", "-q", "--tb=no"],
        cwd=ws,
    )

    coverage_file = ws / "coverage.json"
    if not coverage_file.exists():
        cov_source = ",".join(coverage_paths)
        return error(
            "NO_COVERAGE_DATA",
            "No coverage.json found. Run pytest with --cov first.",
            suggestion=f"Run 'pytest --cov={cov_source} --cov-report=json'",
        )

    try:
        coverage_data = json.loads(coverage_file.read_text())
        total_coverage = coverage_data.get("totals", {}).get("percent_covered", 0)
    except Exception as e:
        return error(
            "COVERAGE_PARSE_ERROR",
            f"Failed to parse coverage.json: {e}",
            suggestion="Delete coverage.json and re-run tests with --cov",
        )

    config_source = "config" if threshold != 80 else "default"

    result_data = {
        "coverage": round(total_coverage, 1),
        "threshold": threshold,
        "warn_threshold": warn_threshold,
        "config_source": config_source,
    }

    if total_coverage < threshold:
        return error(
            "COVERAGE_TOO_LOW",
            f"Coverage {total_coverage:.1f}% is below threshold {threshold}%",
            suggestion=f"Add tests to reach {threshold}% coverage",
        )

    if total_coverage < warn_threshold:
        return success(
            data={**result_data, "status": "warning"},
            reasoning=f"Coverage {total_coverage:.1f}% is below target {warn_threshold}%",
        )

    return success(
        data={**result_data, "status": "passing"},
        reasoning=f"Coverage {total_coverage:.1f}% meets threshold {threshold}%",
    )


def _collect_staged_deps(ws: Path) -> list[str]:
    """Collect dependency names from staged git changes."""
    packages: list[str] = []

    result = subprocess.run(
        ["git", "diff", "--cached", "--unified=0", "pyproject.toml"],
        cwd=ws, capture_output=True, text=True,
    )
    if result.returncode == 0:
        for line in result.stdout.splitlines():
            if line.startswith("+") and not line.startswith("+++"):
                matches = re.findall(r'["\']?([a-zA-Z0-9_-]+)(?:[>=<\[].+)?["\']?,?', line)
                for match in matches:
                    if match and not match.startswith("#"):
                        packages.append(match.lower())

    req_file = ws / "requirements.txt"
    if req_file.exists():
        result = subprocess.run(
            ["git", "diff", "--cached", "--unified=0", "requirements.txt"],
            cwd=ws, capture_output=True, text=True,
        )
        if result.returncode == 0:
            for line in result.stdout.splitlines():
                if line.startswith("+") and not line.startswith("+++"):
                    match = re.match(r"\+([a-zA-Z0-9_-]+)", line)
                    if match:
                        packages.append(match.group(1).lower())

    return packages


def _collect_all_deps(ws: Path) -> list[str]:
    """Collect all declared dependency names from pyproject.toml."""
    import tomllib

    packages: list[str] = []
    pyproject = ws / "pyproject.toml"
    if pyproject.exists():
        data = tomllib.loads(pyproject.read_text(encoding="utf-8"))
        deps = data.get("project", {}).get("dependencies", [])
        for dep in deps:
            match = re.match(r"([a-zA-Z0-9_-]+)", dep)
            if match:
                packages.append(match.group(1).lower())
    return packages


async def _check_pkg_version(
    pkg: str,
    client: object,
    max_major: int,
    max_minor: int,
    outdated: list[str],
    errors: list[dict],
    warnings: list[dict],
) -> None:
    """Check a single package against PyPI and classify staleness."""
    result = subprocess.run(["pip", "show", pkg], capture_output=True, text=True)
    if result.returncode != 0:
        return

    installed_version = None
    for line in result.stdout.splitlines():
        if line.startswith("Version:"):
            installed_version = line.split(":", 1)[1].strip()
            break
    if not installed_version:
        return

    response = await client.get(f"https://pypi.org/pypi/{pkg}/json")  # type: ignore[union-attr]
    if response.status_code != 200:
        return

    latest_version = response.json().get("info", {}).get("version", "")
    if not latest_version:
        return

    installed_parts = _parse_version(installed_version)
    latest_parts = _parse_version(latest_version)
    major_behind = latest_parts[0] - installed_parts[0]
    minor_behind = latest_parts[1] - installed_parts[1] if major_behind == 0 else 0

    if major_behind > max_major:
        errors.append({
            "package": pkg, "installed": installed_version,
            "latest": latest_version, "major_behind": major_behind,
        })
        outdated.append(pkg)
    elif major_behind > 0 or minor_behind > max_minor:
        warnings.append({
            "package": pkg, "installed": installed_version,
            "latest": latest_version, "major_behind": major_behind,
            "minor_behind": minor_behind,
        })
        outdated.append(pkg)


async def dev_check_deps(staged_only: bool = True) -> CommandResult[dict]:
    """Check that dependencies are up-to-date.

    Requires httpx: pip install botcore[quality]
    """
    try:
        import httpx
    except ImportError:
        return error(
            "MISSING_HTTPX",
            "httpx package required for dependency checking",
            suggestion="Install with: pip install botcore[quality]",
        )

    ws = find_workspace()
    if not ws:
        return error(
            "NO_WORKSPACE",
            "Could not find workspace root",
            suggestion="Run from within a Git repository",
        )

    config = load_config(workspace=ws)
    packages_to_check = _collect_staged_deps(ws) if staged_only else _collect_all_deps(ws)

    if not packages_to_check:
        return success(
            data={"checked": 0, "outdated": [], "errors": [], "warnings": []},
            reasoning="No new dependencies to check",
        )

    packages_to_check = list(set(packages_to_check))
    outdated: list[str] = []
    errors: list[dict] = []
    warnings: list[dict] = []

    async with httpx.AsyncClient(timeout=10.0) as client:
        for pkg in packages_to_check:
            try:
                await _check_pkg_version(
                    pkg, client, config.deps_max_major_behind,
                    config.deps_max_minor_behind, outdated, errors, warnings,
                )
            except Exception:
                continue

    result_data = {
        "checked": len(packages_to_check), "packages": packages_to_check,
        "outdated": outdated, "errors": errors, "warnings": warnings,
    }

    if errors:
        pkg_list = ", ".join(
            f"{e['package']} ({e['installed']} → {e['latest']})" for e in errors
        )
        return error(
            "DEPS_TOO_OLD",
            f"Packages significantly outdated: {pkg_list}",
            suggestion="Update with: pip install --upgrade "
            + " ".join(e["package"] for e in errors),
        )

    if warnings:
        pkg_list = ", ".join(w["package"] for w in warnings)
        return success(
            data=result_data,
            reasoning=f"{len(warnings)} package(s) behind latest: {pkg_list}",
        )

    return success(
        data=result_data,
        reasoning=f"All {len(packages_to_check)} checked packages are up-to-date",
    )
