"""Tests for CLI commands."""

import json
from unittest.mock import MagicMock, patch

from typer.testing import CliRunner

from arxivy.api.types import SortBy
from arxivy.cli import app
from arxivy.models import Author, Paper, PaperListMeta

runner = CliRunner()


def _mock_client(return_value=None, side_effect=None):
    """Create a mock ArxivClient context manager."""
    mock = MagicMock()
    mock.__enter__ = MagicMock(return_value=mock)
    mock.__exit__ = MagicMock(return_value=False)
    return mock


class TestVersion:
    def test_version_flag(self):
        result = runner.invoke(app, ["--version"])
        assert result.exit_code == 0
        assert "arxivy" in result.output

    def test_no_args_shows_help(self):
        result = runner.invoke(app, [])
        # no_args_is_help causes exit code 0 or 2 depending on typer version
        assert result.exit_code in (0, 2)


class TestSearchCommand:
    def test_search_json_output(self):
        mock_client = _mock_client()
        from arxivy.models import PaperListResult

        mock_client.search.return_value = PaperListResult(
            results=[
                Paper(
                    arxiv_id="1706.03762",
                    title="Attention Is All You Need",
                    authors=[Author(name="Ashish Vaswani")],
                    published="2017-06-12T17:57:34Z",
                    primary_category="cs.CL",
                    categories=["cs.CL"],
                    summary="Test.",
                ),
            ],
            meta=PaperListMeta(total_results=1, query="attention"),
        )

        with patch("arxivy.commands.ArxivClient", return_value=mock_client):
            result = runner.invoke(app, ["search", "attention", "--json"])

        assert result.exit_code == 0
        data = json.loads(result.output)
        assert "results" in data
        assert len(data["results"]) == 1
        assert data["results"][0]["title"] == "Attention Is All You Need"

    def test_search_bibtex_output(self):
        mock_client = _mock_client()
        from arxivy.models import PaperListResult

        mock_client.search.return_value = PaperListResult(
            results=[
                Paper(
                    arxiv_id="1706.03762",
                    title="Attention Is All You Need",
                    authors=[Author(name="Ashish Vaswani")],
                    published="2017-06-12T17:57:34Z",
                    primary_category="cs.CL",
                    categories=["cs.CL"],
                ),
            ],
            meta=PaperListMeta(total_results=1, query="attention"),
        )

        with patch("arxivy.commands.ArxivClient", return_value=mock_client):
            result = runner.invoke(app, ["search", "attention", "--bibtex"])

        assert result.exit_code == 0
        assert "@misc{" in result.output
        assert "Attention Is All You Need" in result.output

    def test_search_passes_options(self):
        mock_client = _mock_client()
        from arxivy.models import PaperListResult

        mock_client.search.return_value = PaperListResult(results=[], meta=PaperListMeta(total_results=0))

        with patch("arxivy.commands.ArxivClient", return_value=mock_client):
            result = runner.invoke(
                app,
                ["search", "test", "-c", "cs.AI", "--limit", "20", "--sort", "submittedDate"],
            )

        assert result.exit_code == 0
        mock_client.search.assert_called_once_with(
            query="test",
            limit=20,
            offset=0,
            category="cs.AI",
            sort_by=SortBy.SUBMITTED_DATE,
            sort_order=None,
        )

    def test_search_error_json_flag_outputs_json(self):
        mock_client = _mock_client()
        from arxivy.api.errors import ArxivError

        mock_client.search.side_effect = ArxivError("Boom", code="TEST")

        with patch("arxivy.commands.ArxivClient", return_value=mock_client):
            result = runner.invoke(app, ["search", "attention", "--json"])

        assert result.exit_code == 1
        data = json.loads(result.output)
        assert data["error"]["code"] == "TEST"
        assert data["error"]["message"] == "Boom"


class TestPaperCommand:
    def test_single_paper_json(self):
        mock_client = _mock_client()
        mock_client.get_paper.return_value = Paper(
            arxiv_id="1706.03762",
            title="Attention Is All You Need",
            authors=[Author(name="Ashish Vaswani")],
            published="2017-06-12T17:57:34Z",
            primary_category="cs.CL",
            categories=["cs.CL"],
            summary="The Transformer.",
        )

        with patch("arxivy.commands.ArxivClient", return_value=mock_client):
            result = runner.invoke(app, ["paper", "1706.03762", "--json"])

        assert result.exit_code == 0
        data = json.loads(result.output)
        assert data["result"]["title"] == "Attention Is All You Need"
        mock_client.get_paper.assert_called_once_with("1706.03762")

    def test_multiple_papers_json(self):
        mock_client = _mock_client()
        mock_client.get_papers.return_value = [
            Paper(
                arxiv_id="1706.03762",
                title="Paper 1",
                published="2017-01-01T00:00:00Z",
                primary_category="cs.CL",
            ),
            Paper(
                arxiv_id="2010.11929",
                title="Paper 2",
                published="2020-01-01T00:00:00Z",
                primary_category="cs.CV",
            ),
        ]

        with patch("arxivy.commands.ArxivClient", return_value=mock_client):
            result = runner.invoke(app, ["paper", "1706.03762", "2010.11929", "--json"])

        assert result.exit_code == 0
        data = json.loads(result.output)
        assert len(data["results"]) == 2
        mock_client.get_papers.assert_called_once_with(["1706.03762", "2010.11929"])

    def test_paper_bibtex(self):
        mock_client = _mock_client()
        mock_client.get_paper.return_value = Paper(
            arxiv_id="1706.03762",
            title="Attention Is All You Need",
            authors=[Author(name="Vaswani")],
            published="2017-06-12T17:57:34Z",
            primary_category="cs.CL",
            categories=["cs.CL"],
        )

        with patch("arxivy.commands.ArxivClient", return_value=mock_client):
            result = runner.invoke(app, ["paper", "1706.03762", "--bibtex"])

        assert result.exit_code == 0
        assert "@misc{" in result.output
        assert "eprint = {1706.03762}" in result.output


class TestBibtexCommand:
    def test_single_bibtex(self):
        mock_client = _mock_client()
        mock_client.get_paper.return_value = Paper(
            arxiv_id="1706.03762",
            title="Attention Is All You Need",
            authors=[Author(name="Ashish Vaswani")],
            published="2017-06-12T17:57:34Z",
            primary_category="cs.CL",
        )

        with patch("arxivy.commands.ArxivClient", return_value=mock_client):
            result = runner.invoke(app, ["bibtex", "1706.03762"])

        assert result.exit_code == 0
        assert "@misc{vaswani2017attention" in result.output
        assert "eprint = {1706.03762}" in result.output
        assert "archiveprefix = {arXiv}" in result.output

    def test_batch_bibtex(self):
        mock_client = _mock_client()
        mock_client.get_papers.return_value = [
            Paper(
                arxiv_id="1706.03762",
                title="First",
                authors=[Author(name="Author A")],
                published="2017-01-01T00:00:00Z",
                primary_category="cs.CL",
            ),
            Paper(
                arxiv_id="2010.11929",
                title="Second",
                authors=[Author(name="Author B")],
                published="2020-01-01T00:00:00Z",
                primary_category="cs.CV",
            ),
        ]

        with patch("arxivy.commands.ArxivClient", return_value=mock_client):
            result = runner.invoke(app, ["bibtex", "1706.03762", "2010.11929"])

        assert result.exit_code == 0
        assert result.output.count("@misc") == 2


class TestNewCommand:
    def test_new_json(self):
        mock_client = _mock_client()
        from arxivy.models import PaperListResult

        mock_client.get_new.return_value = PaperListResult(
            results=[
                Paper(
                    arxiv_id="2401.00001",
                    title="Latest AI Paper",
                    authors=[Author(name="Researcher")],
                    published="2024-01-01T00:00:00Z",
                    primary_category="cs.AI",
                    categories=["cs.AI"],
                ),
            ],
            meta=PaperListMeta(total_results=100, category="cs.AI"),
        )

        with patch("arxivy.commands.ArxivClient", return_value=mock_client):
            result = runner.invoke(app, ["new", "cs.AI", "--json"])

        assert result.exit_code == 0
        data = json.loads(result.output)
        assert len(data["results"]) == 1
        assert data["meta"]["category"] == "cs.AI"
        mock_client.get_new.assert_called_once_with(category="cs.AI", limit=10)

    def test_new_with_limit(self):
        mock_client = _mock_client()
        from arxivy.models import PaperListResult

        mock_client.get_new.return_value = PaperListResult(results=[], meta=PaperListMeta(total_results=0))

        with patch("arxivy.commands.ArxivClient", return_value=mock_client):
            result = runner.invoke(app, ["new", "cs.AI", "--limit", "5"])

        assert result.exit_code == 0
        mock_client.get_new.assert_called_once_with(category="cs.AI", limit=5)


class TestPaperCommandErrors:
    def test_paper_error_bibtex_flag_outputs_json(self):
        mock_client = _mock_client()
        from arxivy.api.errors import ArxivError

        mock_client.get_paper.side_effect = ArxivError("Nope", code="TEST")

        with patch("arxivy.commands.ArxivClient", return_value=mock_client):
            result = runner.invoke(app, ["paper", "1706.03762", "--bibtex"])

        assert result.exit_code == 1
        data = json.loads(result.output)
        assert data["error"]["code"] == "TEST"
        assert data["error"]["message"] == "Nope"


class TestErrorHandling:
    def test_error_exits_nonzero(self):
        from arxivy.api.errors import ArxivError

        mock_client = _mock_client()
        mock_client.search.side_effect = ArxivError("Something went wrong", code="TEST_ERROR")

        with patch("arxivy.commands.ArxivClient", return_value=mock_client):
            result = runner.invoke(app, ["search", "test"])

        assert result.exit_code == 1
