"""Tests for xnv diagnose module."""

from __future__ import annotations

import os
import stat
import subprocess
from pathlib import Path
from unittest.mock import patch

import pytest

from xnv.diagnose import diagnose, find_venv, Issue, Diagnosis


@pytest.fixture
def tmp_project(tmp_path: Path) -> Path:
    """Create a minimal project directory."""
    (tmp_path / "pyproject.toml").write_text("[project]\nname = 'test'\nversion = '0.1.0'\n")
    return tmp_path


@pytest.fixture
def tmp_project_with_venv(tmp_project: Path) -> Path:
    """Create a project with a working venv."""
    subprocess.run(["python3", "-m", "venv", str(tmp_project / "venv")], check=True)
    return tmp_project


class TestFindVenv:
    def test_finds_venv(self, tmp_project_with_venv: Path):
        result = find_venv(tmp_project_with_venv)
        assert result is not None
        assert result.name == "venv"

    def test_finds_dot_venv(self, tmp_project: Path):
        subprocess.run(["python3", "-m", "venv", str(tmp_project / ".venv")], check=True)
        result = find_venv(tmp_project)
        assert result is not None
        assert result.name == ".venv"

    def test_no_venv(self, tmp_project: Path):
        result = find_venv(tmp_project)
        assert result is None


class TestDiagnose:
    def test_healthy_venv(self, tmp_project_with_venv: Path):
        diag = diagnose(tmp_project_with_venv)
        assert diag.healthy
        assert diag.venv_path is not None
        codes = {i.code for i in diag.issues}
        assert "VENV_PYTHON_OK" in codes
        assert "VENV_PIP_OK" in codes

    def test_no_venv_warning(self, tmp_project: Path):
        diag = diagnose(tmp_project)
        codes = {i.code for i in diag.issues}
        assert "NO_VENV" in codes

    def test_broken_python_symlink(self, tmp_project_with_venv: Path):
        venv = tmp_project_with_venv / "venv"
        python_bin = venv / "bin" / "python"
        # Break the symlink
        python_bin.unlink()
        python_bin.symlink_to("/nonexistent/python3.99")

        diag = diagnose(tmp_project_with_venv)
        assert not diag.healthy
        codes = {i.code for i in diag.errors}
        assert "VENV_BROKEN_PYTHON" in codes

    def test_missing_python(self, tmp_project_with_venv: Path):
        venv = tmp_project_with_venv / "venv"
        python_bin = venv / "bin" / "python"
        # Remove all python binaries
        for p in (venv / "bin").glob("python*"):
            p.unlink()

        diag = diagnose(tmp_project_with_venv)
        assert not diag.healthy
        codes = {i.code for i in diag.errors}
        assert "VENV_NO_PYTHON" in codes

    def test_missing_pip(self, tmp_project_with_venv: Path):
        venv = tmp_project_with_venv / "venv"
        # Remove pip binaries
        for p in (venv / "bin").glob("pip*"):
            p.unlink()

        diag = diagnose(tmp_project_with_venv)
        assert not diag.healthy
        codes = {i.code for i in diag.errors}
        assert "VENV_NO_PIP" in codes

    def test_stale_pyvenv_cfg(self, tmp_project_with_venv: Path):
        venv = tmp_project_with_venv / "venv"
        cfg = venv / "pyvenv.cfg"
        # Write a pyvenv.cfg pointing to a nonexistent directory
        cfg.write_text("home = /nonexistent/bin\ninclude-system-site-packages = false\n")

        diag = diagnose(tmp_project_with_venv)
        codes = {i.code for i in diag.errors}
        assert "VENV_STALE_HOME" in codes

    def test_no_pyproject_warning(self, tmp_path: Path):
        diag = diagnose(tmp_path)
        codes = {i.code for i in diag.issues}
        assert "NO_PYPROJECT" in codes

    def test_conda_detected(self, tmp_project: Path):
        with patch.dict(os.environ, {"CONDA_DEFAULT_ENV": "base"}):
            diag = diagnose(tmp_project)
        codes = {i.code for i in diag.issues}
        assert "CONDA_ACTIVE" in codes

    def test_venv_mismatch(self, tmp_project_with_venv: Path):
        with patch.dict(os.environ, {"VIRTUAL_ENV": "/some/other/venv"}):
            diag = diagnose(tmp_project_with_venv)
        codes = {i.code for i in diag.issues}
        assert "VENV_MISMATCH" in codes


class TestDiagnosisProperties:
    def test_healthy_with_no_errors(self):
        d = Diagnosis(project_dir=Path("."))
        d.issues = [Issue("OK", "info", "all good")]
        assert d.healthy

    def test_unhealthy_with_error(self):
        d = Diagnosis(project_dir=Path("."))
        d.issues = [Issue("FAIL", "error", "broken")]
        assert not d.healthy

    def test_errors_filter(self):
        d = Diagnosis(project_dir=Path("."))
        d.issues = [
            Issue("OK", "info", "fine"),
            Issue("WARN", "warn", "meh"),
            Issue("FAIL", "error", "broken"),
        ]
        assert len(d.errors) == 1
        assert len(d.warnings) == 1
