"""Tests for CLI commands."""

from __future__ import annotations

import json
from unittest.mock import AsyncMock, MagicMock, patch

from click.testing import CliRunner

from edinet_mcp.cli import cli


def _async_client_mock(**method_returns: object) -> MagicMock:
    """Create a mock EdinetClient that supports ``async with``."""
    instance = MagicMock()
    instance.__aenter__ = AsyncMock(return_value=instance)
    instance.__aexit__ = AsyncMock(return_value=False)
    for name, value in method_returns.items():
        setattr(instance, name, AsyncMock(return_value=value))
    return instance


@patch("edinet_mcp.client.EdinetClient")
class TestSearchCommand:
    def test_search_basic(self, mock_cls, sample_company):
        mock_cls.return_value = _async_client_mock(search_companies=[sample_company])
        runner = CliRunner()
        result = runner.invoke(cli, ["search", "トヨタ"])
        assert result.exit_code == 0
        assert "E02144" in result.output
        assert "7203" in result.output
        assert "トヨタ自動車株式会社" in result.output

    def test_search_no_results(self, mock_cls):
        mock_cls.return_value = _async_client_mock(search_companies=[])
        runner = CliRunner()
        result = runner.invoke(cli, ["search", "存在しない企業"])
        assert result.exit_code == 0
        assert "No companies found" in result.output

    def test_search_json_output(self, mock_cls, sample_company):
        mock_cls.return_value = _async_client_mock(search_companies=[sample_company])
        runner = CliRunner()
        result = runner.invoke(cli, ["search", "トヨタ", "--json-output"])
        assert result.exit_code == 0
        data = json.loads(result.output)
        assert len(data) == 1
        assert data[0]["edinet_code"] == "E02144"

    def test_search_limit(self, mock_cls, sample_company):
        companies = [sample_company] * 5
        mock_cls.return_value = _async_client_mock(search_companies=companies)
        runner = CliRunner()
        result = runner.invoke(cli, ["search", "トヨタ", "--limit", "2"])
        assert result.exit_code == 0
        assert result.output.count("E02144") == 2

    def test_search_missing_query(self, mock_cls):
        runner = CliRunner()
        result = runner.invoke(cli, ["search"])
        assert result.exit_code != 0


@patch("edinet_mcp.client.EdinetClient")
class TestStatementsCommand:
    def _make_stmt(self, sample_filing, sample_statement_data):
        from edinet_mcp.models import AccountingStandard, FinancialStatement

        return FinancialStatement(
            filing=sample_filing,
            income_statement=sample_statement_data,
            accounting_standard=AccountingStandard.IFRS,
        )

    def test_statements_table(self, mock_cls, sample_filing, sample_statement_data):
        stmt = self._make_stmt(sample_filing, sample_statement_data)
        mock_cls.return_value = _async_client_mock(get_financial_statements=stmt)
        runner = CliRunner()
        result = runner.invoke(cli, ["statements", "-c", "E02144", "-s", "income_statement"])
        assert result.exit_code == 0
        assert "Filing:" in result.output
        assert "IFRS" in result.output

    def test_statements_json(self, mock_cls, sample_filing, sample_statement_data):
        stmt = self._make_stmt(sample_filing, sample_statement_data)
        mock_cls.return_value = _async_client_mock(get_financial_statements=stmt)
        runner = CliRunner()
        result = runner.invoke(
            cli,
            ["statements", "-c", "E02144", "-s", "income_statement", "-f", "json"],
        )
        assert result.exit_code == 0
        data = json.loads(result.output.split("\n\n", 1)[1])
        assert isinstance(data, list)

    def test_statements_csv(self, mock_cls, sample_filing, sample_statement_data):
        stmt = self._make_stmt(sample_filing, sample_statement_data)
        mock_cls.return_value = _async_client_mock(get_financial_statements=stmt)
        runner = CliRunner()
        result = runner.invoke(
            cli,
            ["statements", "-c", "E02144", "-s", "income_statement", "-f", "csv"],
        )
        assert result.exit_code == 0
        assert "element" in result.output  # CSV header

    def test_statements_not_found(self, mock_cls, sample_filing, sample_statement_data):
        stmt = self._make_stmt(sample_filing, sample_statement_data)
        mock_cls.return_value = _async_client_mock(get_financial_statements=stmt)
        runner = CliRunner()
        result = runner.invoke(cli, ["statements", "-c", "E02144", "-s", "balance_sheet"])
        assert result.exit_code != 0
        assert "No balance_sheet data found" in result.output

    def test_statements_value_error(self, mock_cls):
        instance = _async_client_mock()
        instance.get_financial_statements = AsyncMock(
            side_effect=ValueError("Invalid EDINET code")
        )
        mock_cls.return_value = instance
        runner = CliRunner()
        result = runner.invoke(cli, ["statements", "-c", "INVALID"])
        assert result.exit_code != 0
        assert "Error:" in result.output

    def test_statements_missing_code(self, mock_cls):
        runner = CliRunner()
        result = runner.invoke(cli, ["statements"])
        assert result.exit_code != 0


@patch("edinet_mcp.client.EdinetClient")
@patch("edinet_mcp._screening.screen_companies")
class TestScreenCommand:
    def _make_result(self, companies=None, errors=None):
        results = companies or []
        return {"results": results, "errors": errors or [], "count": len(results)}

    def _sample_row(self, code="E02144", name="トヨタ自動車株式会社"):
        return {
            "edinet_code": code,
            "company_name": name,
            "period_end": "2025-03-31",
            "accounting_standard": "IFRS",
            "profitability": {
                "営業利益率": "11.87%",
                "ROE": "12.50%",
            },
            "stability": {
                "自己資本比率": "41.60%",
            },
        }

    def test_screen_table(self, mock_screen, mock_client_cls):
        mock_client_cls.return_value = _async_client_mock()
        mock_screen.return_value = self._make_result(companies=[self._sample_row()])
        runner = CliRunner()
        result = runner.invoke(cli, ["screen", "E02144"])
        assert result.exit_code == 0
        assert "E02144" in result.output
        assert "Screening: 1 companies" in result.output

    def test_screen_json(self, mock_screen, mock_client_cls):
        mock_client_cls.return_value = _async_client_mock()
        mock_screen.return_value = self._make_result(companies=[self._sample_row()])
        runner = CliRunner()
        result = runner.invoke(cli, ["screen", "E02144", "--format", "json"])
        assert result.exit_code == 0
        data = json.loads(result.output)
        assert data["count"] == 1
        assert len(data["results"]) == 1

    def test_screen_multiple_companies(self, mock_screen, mock_client_cls):
        mock_client_cls.return_value = _async_client_mock()
        mock_screen.return_value = self._make_result(
            companies=[
                self._sample_row("E02144", "トヨタ自動車株式会社"),
                self._sample_row("E01777", "ソニーグループ株式会社"),
            ]
        )
        runner = CliRunner()
        result = runner.invoke(cli, ["screen", "E02144", "E01777"])
        assert result.exit_code == 0
        assert "Screening: 2 companies" in result.output
        assert "E02144" in result.output
        assert "E01777" in result.output

    def test_screen_sort_by(self, mock_screen, mock_client_cls):
        mock_client_cls.return_value = _async_client_mock()
        mock_screen.return_value = self._make_result(companies=[self._sample_row()])
        runner = CliRunner()
        result = runner.invoke(cli, ["screen", "E02144", "--sort-by", "ROE"])
        assert result.exit_code == 0
        # Verify sort_by was passed through
        mock_screen.assert_called_once()
        call_kwargs = mock_screen.call_args
        assert call_kwargs[1]["sort_by"] == "ROE"

    def test_screen_with_errors(self, mock_screen, mock_client_cls):
        mock_client_cls.return_value = _async_client_mock()
        mock_screen.return_value = self._make_result(
            companies=[self._sample_row()],
            errors=[{"edinet_code": "E99999", "error": "Not found"}],
        )
        runner = CliRunner()
        result = runner.invoke(cli, ["screen", "E02144", "E99999"])
        assert result.exit_code == 0
        assert "E02144" in result.output
        assert "[ERROR]" in result.output

    def test_screen_all_errors(self, mock_screen, mock_client_cls):
        mock_client_cls.return_value = _async_client_mock()
        mock_screen.return_value = self._make_result(
            errors=[{"edinet_code": "E99999", "error": "Not found"}],
        )
        runner = CliRunner()
        result = runner.invoke(cli, ["screen", "E99999"])
        assert result.exit_code != 0

    def test_screen_no_args(self, mock_screen, mock_client_cls):
        runner = CliRunner()
        result = runner.invoke(cli, ["screen"])
        assert result.exit_code != 0

    def test_screen_value_error(self, mock_screen, mock_client_cls):
        mock_client_cls.return_value = _async_client_mock()
        mock_screen.side_effect = ValueError("Too many companies: 25 (max 20)")
        runner = CliRunner()
        codes = [f"E{i:05d}" for i in range(25)]
        result = runner.invoke(cli, ["screen", *codes])
        assert result.exit_code != 0
        assert "Error:" in result.output


@patch("edinet_mcp.client.EdinetClient")
class TestTestCommand:
    def test_success(self, mock_cls, sample_company):
        mock_cls.return_value = _async_client_mock(search_companies=[sample_company])
        runner = CliRunner(env={"EDINET_API_KEY": "test_key_abc"})
        result = runner.invoke(cli, ["test"])
        assert result.exit_code == 0
        assert "[OK]   EDINET_API_KEY is set" in result.output
        assert "All checks passed" in result.output

    def test_missing_api_key(self, mock_cls):
        runner = CliRunner(env={"EDINET_API_KEY": ""})
        result = runner.invoke(cli, ["test"])
        assert result.exit_code != 0
        assert "FAIL" in result.output

    def test_api_error(self, mock_cls):
        instance = _async_client_mock()
        instance.search_companies = AsyncMock(side_effect=Exception("Connection refused"))
        mock_cls.return_value = instance
        runner = CliRunner(env={"EDINET_API_KEY": "test_key_abc"})
        result = runner.invoke(cli, ["test"])
        assert result.exit_code != 0
        assert "API error" in result.output


class TestVerboseFlag:
    def test_verbose_sets_debug(self):
        runner = CliRunner()
        result = runner.invoke(cli, ["--verbose", "--help"])
        assert result.exit_code == 0
