"""CLI integration tests using typer's test client."""

from __future__ import annotations

import json
from unittest.mock import patch

from typer.testing import CliRunner

from priorart.cli import app

runner = CliRunner()

EMPTY_SCAN_RESULT = {
    "query": "test idea",
    "queries": ["test idea"],
    "sources": [
        {"type": "github", "results": []},
        {"type": "hn", "results": []},
    ],
    "signal": {"score": 0, "verdict": "LOW", "breakdown": {"github": 0, "hn": 0}},
    "meta": {"timestamp": "2026-03-01T00:00:00+00:00", "version": "0.1.0"},
}


def patched_scan(**kwargs):
    """Return a predictable result regardless of inputs."""
    return EMPTY_SCAN_RESULT


class TestCLIHelp:
    def test_help_exits_zero(self):
        result = runner.invoke(app, ["--help"])
        assert result.exit_code == 0

    def test_help_mentions_scan(self):
        result = runner.invoke(app, ["--help"])
        assert "scan" in result.output

    def test_scan_help_exits_zero(self):
        result = runner.invoke(app, ["scan", "--help"])
        assert result.exit_code == 0

    def test_scan_help_mentions_format(self):
        result = runner.invoke(app, ["scan", "--help"])
        assert "--format" in result.output

    def test_scan_help_mentions_sources(self):
        result = runner.invoke(app, ["scan", "--help"])
        assert "--sources" in result.output


class TestCLIScanFormats:
    def test_default_format_is_table(self):
        with patch("priorart.cli.scan", return_value=EMPTY_SCAN_RESULT):
            result = runner.invoke(app, ["scan", "test idea"])
        assert result.exit_code == 0
        assert "Prior Art Scan" in result.output

    def test_json_format_produces_valid_json(self):
        with patch("priorart.cli.scan", return_value=EMPTY_SCAN_RESULT):
            result = runner.invoke(app, ["scan", "test idea", "--format", "json"], env={"GITHUB_TOKEN": "fake"})
        assert result.exit_code == 0
        parsed = json.loads(result.output)
        assert "query" in parsed
        assert "signal" in parsed
        assert "sources" in parsed
        assert "meta" in parsed

    def test_markdown_format_has_header(self):
        with patch("priorart.cli.scan", return_value=EMPTY_SCAN_RESULT):
            result = runner.invoke(app, ["scan", "test idea", "--format", "markdown"])
        assert result.exit_code == 0
        assert "## 🔍 Prior Art Scan" in result.output

    def test_table_format_has_signal(self):
        with patch("priorart.cli.scan", return_value=EMPTY_SCAN_RESULT):
            result = runner.invoke(app, ["scan", "test idea", "--format", "table"])
        assert result.exit_code == 0
        assert "Reality Signal" in result.output

    def test_invalid_format_exits_nonzero(self):
        result = runner.invoke(app, ["scan", "test idea", "--format", "xml"])
        assert result.exit_code != 0

    def test_invalid_format_shows_error(self):
        result = runner.invoke(app, ["scan", "test idea", "--format", "xml"])
        assert "Error" in result.output or "invalid" in result.output.lower()


class TestCLIScanSources:
    def test_invalid_source_exits_nonzero(self):
        result = runner.invoke(app, ["scan", "test idea", "--sources", "producthunt"])
        assert result.exit_code != 0

    def test_hn_only_source_accepted(self):
        with patch("priorart.cli.scan", return_value=EMPTY_SCAN_RESULT):
            result = runner.invoke(
                app, ["scan", "test idea", "--sources", "hn", "--format", "json"]
            )
        assert result.exit_code == 0

    def test_github_only_warns_without_token(self):
        result = runner.invoke(
            app,
            ["scan", "test idea", "--sources", "github"],
            env={"GITHUB_TOKEN": ""},
        )
        # Should warn but not crash; or produce output
        assert result.exit_code == 0

    def test_github_token_env_var_respected(self):
        with patch("priorart.cli.scan", return_value=EMPTY_SCAN_RESULT) as mock_scan:
            runner.invoke(
                app,
                ["scan", "test idea", "--format", "json"],
                env={"GITHUB_TOKEN": "ghp_test_token"},
            )
        call_kwargs = mock_scan.call_args[1]
        assert call_kwargs.get("github_token") == "ghp_test_token"


class TestCLIScanOutputStructure:
    def test_json_output_has_query_field(self):
        with patch("priorart.cli.scan", return_value=EMPTY_SCAN_RESULT):
            result = runner.invoke(
                app, ["scan", "my test idea", "--format", "json"], env={"GITHUB_TOKEN": "fake"}
            )
        data = json.loads(result.output)
        assert data["query"] == "test idea"  # from mock

    def test_json_output_signal_has_score(self):
        with patch("priorart.cli.scan", return_value=EMPTY_SCAN_RESULT):
            result = runner.invoke(app, ["scan", "test idea", "--format", "json"], env={"GITHUB_TOKEN": "fake"})
        data = json.loads(result.output)
        assert "score" in data["signal"]
        assert isinstance(data["signal"]["score"], int)

    def test_json_output_signal_has_verdict(self):
        with patch("priorart.cli.scan", return_value=EMPTY_SCAN_RESULT):
            result = runner.invoke(app, ["scan", "test idea", "--format", "json"], env={"GITHUB_TOKEN": "fake"})
        data = json.loads(result.output)
        assert data["signal"]["verdict"] in ("LOW", "MODERATE", "HIGH")

    def test_json_output_has_meta_version(self):
        with patch("priorart.cli.scan", return_value=EMPTY_SCAN_RESULT):
            result = runner.invoke(app, ["scan", "test idea", "--format", "json"], env={"GITHUB_TOKEN": "fake"})
        data = json.loads(result.output)
        assert "version" in data["meta"]
        assert data["meta"]["version"] == "0.1.0"

    def test_json_sources_have_type_and_results(self):
        with patch("priorart.cli.scan", return_value=EMPTY_SCAN_RESULT):
            result = runner.invoke(app, ["scan", "test idea", "--format", "json"], env={"GITHUB_TOKEN": "fake"})
        data = json.loads(result.output)
        for source in data["sources"]:
            assert "type" in source
            assert "results" in source
            assert isinstance(source["results"], list)


class TestCLIScanTokenAndSources:
    def test_github_token_flag_passed_to_scan(self):
        with patch("priorart.cli.scan", return_value=EMPTY_SCAN_RESULT) as mock_scan:
            runner.invoke(
                app,
                [
                    "scan", "test idea",
                    "--github-token", "ghp_explicit_flag",
                    "--format", "json",
                ],
            )
        kwargs = mock_scan.call_args[1]
        assert kwargs.get("github_token") == "ghp_explicit_flag"

    def test_both_sources_comma_separated_accepted(self):
        with patch("priorart.cli.scan", return_value=EMPTY_SCAN_RESULT) as mock_scan:
            result = runner.invoke(
                app,
                ["scan", "test idea", "--sources", "github,hn", "--format", "json"],
                env={"GITHUB_TOKEN": "ghp_test"},
            )
        assert result.exit_code == 0
        sources_arg = mock_scan.call_args[1].get("sources") or mock_scan.call_args[0][2]
        assert "github" in sources_arg
        assert "hn" in sources_arg

    def test_github_without_token_warning_on_stderr(self):
        result = runner.invoke(
            app,
            ["scan", "test idea", "--sources", "github"],
            env={"GITHUB_TOKEN": ""},
        )
        assert result.exit_code == 0
        # Note appears in combined output (typer CliRunner merges stderr by default)
        stderr = result.stderr if hasattr(result, "stderr") else ""
        assert "Note" in result.output or "Note" in stderr or "rate-limited" in result.output

    def test_sources_passed_to_scan_function(self):
        with patch("priorart.cli.scan", return_value=EMPTY_SCAN_RESULT) as mock_scan:
            runner.invoke(
                app,
                ["scan", "test idea", "--sources", "hn", "--format", "json"],
            )
        kwargs = mock_scan.call_args[1]
        assert kwargs.get("sources") == ["hn"]

    def test_scan_missing_idea_exits_nonzero(self):
        result = runner.invoke(app, ["scan"])
        assert result.exit_code != 0
