"""Tests for glreview.cli_ci module."""

from __future__ import annotations

from pathlib import Path
from unittest.mock import patch

import pytest
from click.testing import CliRunner

from glreview.cli import main
from glreview.registry import ModuleStatus, Registry, save_registry


@pytest.fixture
def runner():
    return CliRunner()


@pytest.fixture
def project_dir(temp_dir):
    """Create a project with source files and pyproject.toml."""
    src = temp_dir / "src"
    src.mkdir()
    (src / "main.py").write_text("def hello():\n    return 'Hello'\n")
    (src / "utils.py").write_text("def add(a, b):\n    return a + b\n")

    (temp_dir / "pyproject.toml").write_text(
        '[tool.glreview]\nsources = ["src/**/*.py"]\n'
    )
    return temp_dir


@pytest.fixture
def registry_all_reviewed(project_dir):
    """Registry where all modules are reviewed."""
    registry = Registry()
    registry.set(
        ModuleStatus(
            path="src/main.py", status="reviewed", lines=2,
            reviewers=["@alice"], last_reviewed_commit="abc123",
        )
    )
    registry.set(
        ModuleStatus(
            path="src/utils.py", status="reviewed", lines=2,
            reviewers=["@bob"], last_reviewed_commit="abc123",
        )
    )
    reg_path = project_dir / ".glreview" / "registry.json"
    reg_path.parent.mkdir(parents=True, exist_ok=True)
    save_registry(registry, reg_path)
    return project_dir


@pytest.fixture
def registry_partial(project_dir):
    """Registry where only some modules are reviewed."""
    registry = Registry()
    registry.set(
        ModuleStatus(
            path="src/main.py", status="reviewed", lines=2,
            reviewers=["@alice"], last_reviewed_commit="abc123",
        )
    )
    registry.set(ModuleStatus(path="src/utils.py", status="needs_review", lines=2))
    reg_path = project_dir / ".glreview" / "registry.json"
    reg_path.parent.mkdir(parents=True, exist_ok=True)
    save_registry(registry, reg_path)
    return project_dir


class TestCICommand:
    """Tests for the glreview ci command."""

    @patch("glreview.cli_ci.get_current_commit", return_value="abc123")
    @patch("glreview.review_state.has_changes_since", return_value=False)
    def test_full_coverage(self, _mock_changes, _mock_commit, runner, registry_all_reviewed, monkeypatch):
        monkeypatch.chdir(registry_all_reviewed)
        result = runner.invoke(main, ["ci"])

        assert result.exit_code == 0
        assert "100%" in result.output

    @patch("glreview.cli_ci.get_current_commit", return_value="abc123")
    @patch("glreview.review_state.has_changes_since", return_value=False)
    def test_partial_coverage(self, _mock_changes, _mock_commit, runner, registry_partial, monkeypatch):
        monkeypatch.chdir(registry_partial)
        result = runner.invoke(main, ["ci"])

        assert result.exit_code == 0
        assert "50%" in result.output
        assert "1/2" in result.output

    @patch("glreview.cli_ci.get_current_commit", return_value="abc123")
    @patch("glreview.review_state.has_changes_since", return_value=False)
    def test_fail_under_passes(self, _mock_changes, _mock_commit, runner, registry_all_reviewed, monkeypatch):
        monkeypatch.chdir(registry_all_reviewed)
        result = runner.invoke(main, ["ci", "--fail-under", "100"])

        assert result.exit_code == 0

    @patch("glreview.cli_ci.get_current_commit", return_value="abc123")
    @patch("glreview.review_state.has_changes_since", return_value=False)
    def test_fail_under_fails(self, _mock_changes, _mock_commit, runner, registry_partial, monkeypatch):
        monkeypatch.chdir(registry_partial)
        result = runner.invoke(main, ["ci", "--fail-under", "100"])

        assert result.exit_code == 1
        assert "FAIL" in result.output

    @patch("glreview.cli_ci.get_current_commit", return_value="abc123")
    @patch("glreview.review_state.has_changes_since", return_value=False)
    def test_report_writes_file(self, _mock_changes, _mock_commit, runner, registry_all_reviewed, monkeypatch):
        monkeypatch.chdir(registry_all_reviewed)
        report_file = registry_all_reviewed / "COVERAGE.md"

        result = runner.invoke(main, ["ci", "--report", str(report_file)])

        assert result.exit_code == 0
        assert report_file.exists()
        content = report_file.read_text()
        assert "Review Coverage" in content
        assert f"Report written to {report_file}" in result.output

    @patch("glreview.cli_ci.get_current_commit", return_value="abc123")
    @patch("glreview.review_state.has_changes_since", return_value=False)
    def test_out_of_sync_warns(self, _mock_changes, _mock_commit, runner, project_dir, monkeypatch):
        """When registry is missing modules, CI warns about sync."""
        # Create registry with only one module (main.py missing)
        registry = Registry()
        registry.set(ModuleStatus(
            path="src/utils.py", status="reviewed", lines=2,
            reviewers=["@a"], last_reviewed_commit="abc123",
        ))
        reg_path = project_dir / ".glreview" / "registry.json"
        reg_path.parent.mkdir(parents=True, exist_ok=True)
        save_registry(registry, reg_path)

        monkeypatch.chdir(project_dir)
        result = runner.invoke(main, ["ci"])

        assert "out of sync" in result.output.lower()
        assert "+ src/main.py" in result.output

    @patch("glreview.cli_ci.get_current_commit", return_value="abc123")
    @patch("glreview.review_state.has_changes_since", return_value=False)
    def test_removed_module_detected(self, _mock_changes, _mock_commit, runner, project_dir, monkeypatch):
        """When registry has modules for deleted files, CI warns."""
        registry = Registry()
        registry.set(ModuleStatus(
            path="src/main.py", status="reviewed", lines=2,
            reviewers=["@a"], last_reviewed_commit="abc123",
        ))
        registry.set(ModuleStatus(
            path="src/utils.py", status="reviewed", lines=2,
            reviewers=["@a"], last_reviewed_commit="abc123",
        ))
        registry.set(ModuleStatus(path="src/gone.py", status="needs_review", lines=10))
        reg_path = project_dir / ".glreview" / "registry.json"
        reg_path.parent.mkdir(parents=True, exist_ok=True)
        save_registry(registry, reg_path)

        monkeypatch.chdir(project_dir)
        result = runner.invoke(main, ["ci"])

        assert "- src/gone.py" in result.output

    def test_no_registry_exits(self, runner, project_dir, monkeypatch):
        """CI fails if no registry exists."""
        monkeypatch.chdir(project_dir)
        result = runner.invoke(main, ["ci"])

        assert result.exit_code == 1
        assert "No registry found" in result.output

    @patch("glreview.cli_ci.get_current_commit", return_value="abc123")
    @patch("glreview.review_state.has_changes_since", return_value=False)
    def test_fail_under_with_report(self, _mock_changes, _mock_commit, runner, registry_partial, monkeypatch):
        """Report is written even when --fail-under triggers failure."""
        monkeypatch.chdir(registry_partial)
        report_file = registry_partial / "COVERAGE.md"

        result = runner.invoke(main, ["ci", "--report", str(report_file), "--fail-under", "100"])

        assert result.exit_code == 1
        assert report_file.exists()
        assert "FAIL" in result.output

    @patch("glreview.cli_ci.get_current_commit", return_value="abc123")
    @patch("glreview.review_state.has_changes_since", return_value=True)
    def test_stale_modules_fail(self, _mock_changes, _mock_commit, runner, registry_all_reviewed, monkeypatch):
        """CI fails when reviewed modules have changed since their review."""
        monkeypatch.chdir(registry_all_reviewed)
        result = runner.invoke(main, ["ci"])

        assert result.exit_code == 1
        assert "have changed" in result.output
        assert "src/main.py" in result.output

    @patch("glreview.cli_ci.get_current_commit", return_value="abc123")
    @patch("glreview.review_state.has_changes_since", return_value=True)
    def test_stale_and_fail_under_both_reported(self, _mock_changes, _mock_commit, runner, registry_partial, monkeypatch):
        """Both stale modules and coverage failure are reported."""
        monkeypatch.chdir(registry_partial)
        result = runner.invoke(main, ["ci", "--fail-under", "100"])

        assert result.exit_code == 1
        assert "have changed" in result.output
        assert "coverage" in result.output.lower()
