"""Tests for the CLI entry point."""

from unittest.mock import MagicMock

from click.testing import CliRunner

from prlens_cli.cli import main
from prlens_store.noop import NoOpStore


def _make_config(github_token="tok", model="anthropic", anthropic_key="ant", openai_key=None):
    return {
        "github_token": github_token,
        "model": model,
        "anthropic_api_key": anthropic_key,
        "openai_api_key": openai_key,
        "guidelines": None,
        "exclude": [],
        "review_draft_prs": False,
        "max_chars_per_file": 20000,
        "batch_limit": 60,
        "store": "noop",
    }


def _patch_common(mocker, config=None, token="tok"):
    """Patch load_config, resolve_github_token, and _build_store for most tests."""
    cfg = config or _make_config()
    mocker.patch("prlens_core.config.load_config", return_value=cfg)
    mocker.patch("prlens_cli.auth.resolve_github_token", return_value=token)
    mock_store = MagicMock(spec=NoOpStore)
    mock_store.list_reviews.return_value = []
    mocker.patch("prlens_cli.cli._build_store", return_value=mock_store)
    return cfg, mock_store


class TestCLIValidation:
    def test_missing_github_token(self, mocker):
        _patch_common(mocker, config=_make_config(github_token=None), token=None)

        result = CliRunner().invoke(main, ["review", "--repo", "owner/repo", "--pr", "1"])
        assert result.exit_code != 0
        assert "token" in result.output.lower() or "GITHUB_TOKEN" in result.output

    def test_missing_anthropic_key(self, mocker):
        _patch_common(mocker, config=_make_config(model="anthropic", anthropic_key=None))

        result = CliRunner().invoke(main, ["review", "--repo", "owner/repo", "--pr", "1"])
        assert result.exit_code != 0
        assert "ANTHROPIC_API_KEY" in result.output

    def test_missing_openai_key(self, mocker):
        _patch_common(mocker, config=_make_config(model="openai", anthropic_key=None, openai_key=None))

        result = CliRunner().invoke(main, ["review", "--repo", "owner/repo", "--pr", "1"])
        assert result.exit_code != 0
        assert "OPENAI_API_KEY" in result.output


class TestCLIRunReview:
    def test_calls_run_review_with_correct_args(self, mocker):
        _patch_common(mocker)
        mocker.patch("prlens_cli.commands.review.get_repo", return_value=MagicMock())
        mocker.patch("prlens_cli.commands.review.get_pull", return_value=MagicMock(title="Fix bug"))
        mock_run = mocker.patch("prlens_cli.commands.review.run_review", return_value=None)

        CliRunner().invoke(main, ["review", "--repo", "owner/repo", "--pr", "42", "--yes"])

        mock_run.assert_called_once()
        kwargs = mock_run.call_args.kwargs
        assert kwargs["pr_number"] == 42
        assert kwargs["auto_confirm"] is True
        assert kwargs["shadow"] is False
        assert kwargs["force_full"] is False

    def test_shadow_flag_passed_through(self, mocker):
        _patch_common(mocker)
        mocker.patch("prlens_cli.commands.review.get_repo", return_value=MagicMock())
        mocker.patch("prlens_cli.commands.review.get_pull", return_value=MagicMock(title="Fix bug"))
        mock_run = mocker.patch("prlens_cli.commands.review.run_review", return_value=None)

        CliRunner().invoke(main, ["review", "--repo", "owner/repo", "--pr", "1", "--shadow"])

        assert mock_run.call_args.kwargs["shadow"] is True

    def test_full_review_flag_passed_through(self, mocker):
        _patch_common(mocker)
        mocker.patch("prlens_cli.commands.review.get_repo", return_value=MagicMock())
        mocker.patch("prlens_cli.commands.review.get_pull", return_value=MagicMock(title="Fix bug"))
        mock_run = mocker.patch("prlens_cli.commands.review.run_review", return_value=None)

        CliRunner().invoke(main, ["review", "--repo", "owner/repo", "--pr", "1", "--full-review"])

        assert mock_run.call_args.kwargs["force_full"] is True

    def test_review_summary_saved_to_store(self, mocker):
        """When run_review returns a ReviewSummary, it must be persisted to the store."""
        from prlens_core.reviewer import ReviewSummary

        summary = ReviewSummary(
            repo="owner/repo",
            pr_number=1,
            head_sha="a" * 40,
            event="APPROVE",
        )
        _, mock_store = _patch_common(mocker)
        mocker.patch("prlens_cli.commands.review.get_repo", return_value=MagicMock())
        mocker.patch("prlens_cli.commands.review.get_pull", return_value=MagicMock(title="Fix bug"))
        mocker.patch("prlens_cli.commands.review.run_review", return_value=summary)

        CliRunner().invoke(main, ["review", "--repo", "owner/repo", "--pr", "1", "--yes"])

        mock_store.save.assert_called_once()


class TestCLIInteractive:
    def test_lists_open_prs(self, mocker):
        _patch_common(mocker)
        mocker.patch("prlens_cli.commands.review.get_repo", return_value=MagicMock())
        mocker.patch("prlens_cli.commands.review.get_pull", return_value=MagicMock(title="Fix login bug"))
        mock_pr = MagicMock()
        mock_pr.number = 7
        mock_pr.title = "Fix login bug"
        mocker.patch("prlens_cli.commands.review.get_pull_requests", return_value=[mock_pr])
        mocker.patch("prlens_cli.commands.review.run_review", return_value=None)

        result = CliRunner().invoke(main, ["review", "--repo", "owner/repo"], input="7\n")

        assert "#7" in result.output
        assert "Fix login bug" in result.output

    def test_no_open_prs_exits_early(self, mocker):
        _patch_common(mocker)
        mocker.patch("prlens_cli.commands.review.get_repo", return_value=MagicMock())
        mocker.patch("prlens_cli.commands.review.get_pull_requests", return_value=[])
        mock_run = mocker.patch("prlens_cli.commands.review.run_review", return_value=None)

        result = CliRunner().invoke(main, ["review", "--repo", "owner/repo"])

        assert "No open pull requests" in result.output
        mock_run.assert_not_called()
