"""Tests for the new Typer-based CLI (cli_v2)."""

import httpx
from types import SimpleNamespace
from typer.testing import CliRunner

from autodebug.cli_v2 import (
    _extract_project_id_from_repl_command,
    _format_http_response_error,
    _handle_nested_hikaflow_command,
    _handle_repl_project_init,
    app,
)

runner = CliRunner()


class TestCLIHelp:
    """Test CLI help and basic commands."""

    def test_help_shows_all_commands(self):
        """Test that --help shows all expected commands."""
        result = runner.invoke(app, ["--help"])
        assert result.exit_code == 0
        assert "login" in result.stdout
        assert "logout" in result.stdout
        assert "status" in result.stdout
        assert "errors" in result.stdout
        assert "debug" in result.stdout
        assert "watch" in result.stdout
        assert "inspect" in result.stdout

    def test_help_shows_new_commands(self):
        """Test that --help shows newly ported commands."""
        result = runner.invoke(app, ["--help"])
        assert result.exit_code == 0
        assert "doctor" in result.stdout
        assert "whoami" in result.stdout
        assert "setup" in result.stdout

    def test_version_flag(self):
        """Test --version flag."""
        result = runner.invoke(app, ["--version"])
        assert result.exit_code == 0
        assert "Hikaflow" in result.stdout or "2.0.0" in result.stdout

    def test_global_json_flag(self):
        """Test --json global flag is accepted."""
        result = runner.invoke(app, ["--help"])
        assert result.exit_code == 0
        assert "--json" in result.stdout

    def test_global_no_color_flag(self):
        """Test --no-color global flag is accepted."""
        result = runner.invoke(app, ["--help"])
        assert result.exit_code == 0
        assert "--no-color" in result.stdout

    def test_global_verbose_flag(self):
        """Test --verbose global flag is accepted."""
        result = runner.invoke(app, ["--help"])
        assert result.exit_code == 0
        assert "--verbose" in result.stdout

    def test_login_help(self):
        """Test login --help."""
        result = runner.invoke(app, ["login", "--help"])
        assert result.exit_code == 0
        assert "--device-auth" in result.stdout
        assert "--token" in result.stdout

    def test_debug_help(self):
        """Test debug --help."""
        result = runner.invoke(app, ["debug", "--help"])
        assert result.exit_code == 0
        assert "--model" in result.stdout
        assert "--headless" in result.stdout
        assert "--command" in result.stdout

    def test_watch_help(self):
        """Test watch --help."""
        result = runner.invoke(app, ["watch", "--help"])
        assert result.exit_code == 0
        assert "--interval" in result.stdout
        assert "--auto-analyze" in result.stdout

    def test_errors_help(self):
        """Test errors --help."""
        result = runner.invoke(app, ["errors", "--help"])
        assert result.exit_code == 0
        assert "--limit" in result.stdout
        assert "--env" in result.stdout  # Short form used in CLI

    def test_inspect_help(self):
        """Test inspect --help."""
        result = runner.invoke(app, ["inspect", "--help"])
        assert result.exit_code == 0
        assert "ERROR_ID" in result.stdout

    def test_doctor_help(self):
        """Test doctor --help."""
        result = runner.invoke(app, ["doctor", "--help"])
        assert result.exit_code == 0
        assert "environment" in result.stdout.lower()

    def test_init_help(self):
        """Test init --help."""
        result = runner.invoke(app, ["init", "--help"])
        assert result.exit_code == 0
        assert "--project-id" in result.stdout
        assert "--project-name" in result.stdout

    def test_whoami_help(self):
        """Test whoami --help."""
        result = runner.invoke(app, ["whoami", "--help"])
        assert result.exit_code == 0
        assert "authentication" in result.stdout.lower()

    def test_completion_help(self):
        """Test completion --help."""
        result = runner.invoke(app, ["completion", "--help"])
        assert result.exit_code == 0
        assert "--install" in result.stdout


class TestCLILogout:
    """Test logout command."""

    def test_logout_without_credentials(self, tmp_path, monkeypatch):
        """Test logout when no credentials exist."""
        # Point to empty temp directory
        monkeypatch.setattr("autodebug.auth.HIKAFLOW_DIR", tmp_path)
        monkeypatch.setattr("autodebug.auth.CREDENTIALS_FILE", tmp_path / "credentials.json")

        result = runner.invoke(app, ["logout"])
        assert result.exit_code == 0
        assert "Logged out" in result.stdout


class TestCLILoginToken:
    """Test login with manual token."""

    def test_login_with_token(self, tmp_path, monkeypatch):
        """Test login --token saves credentials."""
        creds_file = tmp_path / "credentials.json"
        monkeypatch.setattr("autodebug.auth.HIKAFLOW_DIR", tmp_path)
        monkeypatch.setattr("autodebug.auth.CREDENTIALS_FILE", creds_file)
        monkeypatch.setattr("autodebug.config.CREDENTIALS_PATH", creds_file)
        monkeypatch.setattr("autodebug.config.CONFIG_DIR", tmp_path)

        result = runner.invoke(app, ["login", "--token", "test-token-123"])
        assert result.exit_code == 0
        assert "Token saved" in result.stdout

        # Verify token was saved
        import json
        saved = json.loads(creds_file.read_text())
        assert saved["token"] == "test-token-123"


class TestCliApiErrorFormatting:
    def test_format_api_error_includes_request_id(self):
        class Resp:
            status_code = 500
            text = "backend unavailable"
            headers = {"x-request-id": "req-123"}

        msg = _format_http_response_error(Resp(), operation="List sessions")
        assert "List sessions failed (500)" in msg
        assert "backend unavailable" in msg
        assert "request_id=req-123" in msg

    def test_errors_command_surfaces_request_id_on_500(self, monkeypatch):
        class Resp:
            status_code = 500
            text = "prod errors down"
            headers = {"x-request-id": "req-prod-500"}

        class FakeAsyncClient:
            async def __aenter__(self):
                return self

            async def __aexit__(self, exc_type, exc, tb):
                return False

            async def get(self, *args, **kwargs):
                return Resp()

        monkeypatch.setattr("autodebug.auth.is_authenticated", lambda: True)
        monkeypatch.setattr("autodebug.auth.load_config", lambda: {"api_base": "https://get.hikaflow.com"})
        monkeypatch.setattr("autodebug.auth.get_credentials", lambda: {"token": "t"})
        monkeypatch.setattr(httpx, "AsyncClient", FakeAsyncClient)

        result = runner.invoke(app, ["errors"])
        assert result.exit_code == 0
        assert "Fetch production errors failed (500)" in result.stdout
        assert "request_id=req-prod-500" in result.stdout

    def test_session_list_surfaces_request_id_on_500(self, monkeypatch):
        class Resp:
            status_code = 500
            text = "sessions down"
            headers = {"x-request-id": "req-sess-500"}

        monkeypatch.setattr("autodebug.auth.is_authenticated", lambda: True)
        monkeypatch.setattr(
            "autodebug.auth.load_config",
            lambda: {"api_base": "https://get.hikaflow.com", "project_id": "proj_123"},
        )
        monkeypatch.setattr("autodebug.auth.get_credentials", lambda: {"token": "t"})
        monkeypatch.setattr(httpx, "get", lambda *args, **kwargs: Resp())

        result = runner.invoke(app, ["session", "list"])
        assert result.exit_code == 1
        assert "List sessions failed (500)" in result.stdout
        assert "request_id=req-sess-500" in result.stdout

    def test_issues_list_surfaces_request_id_on_500(self, monkeypatch):
        class Resp:
            status_code = 500
            text = "issue groups down"
            headers = {"x-request-id": "req-issues-500"}

        monkeypatch.setattr("autodebug.auth.is_authenticated", lambda: True)
        monkeypatch.setattr("autodebug.auth.load_config", lambda: {"api_base": "https://get.hikaflow.com"})
        monkeypatch.setattr("autodebug.auth.get_credentials", lambda: {"token": "t"})
        monkeypatch.setattr(httpx, "get", lambda *args, **kwargs: Resp())

        result = runner.invoke(app, ["issues", "list"])
        assert result.exit_code == 1
        assert "List issue groups failed (500)" in result.stdout
        assert "request_id=req-issues-500" in result.stdout


class TestInteractiveInitParsing:
    def test_extract_project_id_from_slash_init(self):
        pid = _extract_project_id_from_repl_command("/init 550e8400-e29b-41d4-a716-446655440000")
        assert pid == "550e8400-e29b-41d4-a716-446655440000"

    def test_extract_project_id_from_hikaflow_init_line(self):
        pid = _extract_project_id_from_repl_command(
            "hikaflow init --project-id 550e8400-e29b-41d4-a716-446655440000"
        )
        assert pid == "550e8400-e29b-41d4-a716-446655440000"

    def test_handle_repl_project_init_saves_project_id(self, monkeypatch):
        store = {"project_id": None}

        def _load():
            return {}

        def _save(data):
            store["project_id"] = data.get("project_id")

        monkeypatch.setattr("autodebug.config.load_credentials", _load)
        monkeypatch.setattr("autodebug.config.save_credentials", _save)

        handled = _handle_repl_project_init("/init 550e8400-e29b-41d4-a716-446655440000")
        assert handled is True
        assert store["project_id"] == "550e8400-e29b-41d4-a716-446655440000"

    def test_nested_hikaflow_init_command_is_intercepted(self, monkeypatch):
        store = {"project_id": None}

        monkeypatch.setattr("autodebug.config.load_credentials", lambda: {})
        monkeypatch.setattr("autodebug.config.save_credentials", lambda data: store.update(project_id=data.get("project_id")))

        handled = _handle_nested_hikaflow_command(
            "hikaflow init --project-id 550e8400-e29b-41d4-a716-446655440000"
        )
        assert handled is True
        assert store["project_id"] == "550e8400-e29b-41d4-a716-446655440000"

    def test_nested_hikaflow_status_is_intercepted(self):
        handled = _handle_nested_hikaflow_command("hikaflow status")
        assert handled is True


# --- Action prompt detection and tool-choice override ---


class TestActionPromptDetection:
    """Test _is_action_prompt helper for detecting action-oriented prompts."""

    def test_matches_action_words(self):
        from autodebug.cli_v2 import _is_action_prompt

        assert _is_action_prompt("find issues in the codebase") is True
        assert _is_action_prompt("scan for vulnerabilities") is True
        assert _is_action_prompt("run my tests") is True
        assert _is_action_prompt("fix the flaky test") is True
        assert _is_action_prompt("analyze test failures") is True

    def test_rejects_non_action_prompts(self):
        from autodebug.cli_v2 import _is_action_prompt

        assert _is_action_prompt("hello, how are you?") is False
        assert _is_action_prompt("explain what flaky tests are") is False
        assert _is_action_prompt("what is this project about?") is False

    def test_handles_empty_and_edge_cases(self):
        from autodebug.cli_v2 import _is_action_prompt

        assert _is_action_prompt("") is False
        assert _is_action_prompt([]) is False

    def test_handles_multimodal_input(self):
        from autodebug.cli_v2 import _is_action_prompt

        assert _is_action_prompt(["find errors", "image_data"]) is True
        assert _is_action_prompt(["hello there", "image_data"]) is False


class TestForceToolModelSettings:
    """Test _force_tool_model_settings helper for OpenAI tool_choice override."""

    def test_openai_model_returns_settings(self):
        from autodebug.cli_v2 import _force_tool_model_settings
        from autodebug.agent.core import HikaflowDeps

        deps = HikaflowDeps(
            api_base="https://x.com", token="t", project_path="/tmp",
            model_id="openai:gpt-5",
        )
        settings = _force_tool_model_settings(deps)
        assert settings is not None
        assert settings.get("extra_body") == {"tool_choice": "required"}

    def test_anthropic_model_returns_none(self):
        from autodebug.cli_v2 import _force_tool_model_settings
        from autodebug.agent.core import HikaflowDeps

        deps = HikaflowDeps(
            api_base="https://x.com", token="t", project_path="/tmp",
            model_id="anthropic:claude-sonnet-4-5-20250929",
        )
        assert _force_tool_model_settings(deps) is None


class TestTurnLifecycleHelpers:
    """Phase 2 turn lifecycle helper coverage."""

    def test_turn_start_and_budget_events_emitted(self, monkeypatch):
        events = []

        def _fake_log_event(event, **fields):
            events.append((event, fields))

        monkeypatch.setattr("autodebug.debug_log.log_event", _fake_log_event)
        from autodebug.cli_v2 import _turn_start

        deps = SimpleNamespace(_skill_budget=3)
        turn_id = _turn_start(
            deps,
            backend="pydantic-ai",
            prompt="scan the code",
            is_action_prompt=True,
        )
        assert isinstance(turn_id, str)
        assert events[0][0] == "turn.start"
        assert events[1][0] == "turn.tool_budget"
        assert events[0][1]["turn_id"] == turn_id

    def test_turn_outcome_emitted_once(self, monkeypatch):
        events = []

        def _fake_log_event(event, **fields):
            events.append((event, fields))

        monkeypatch.setattr("autodebug.debug_log.log_event", _fake_log_event)
        from autodebug.cli_v2 import _turn_outcome

        deps = SimpleNamespace(_current_turn_id="t-1", _turn_outcome_emitted=False)
        _turn_outcome(deps, outcome="DELIVER", reason_code="answered", delivered_artifact_kind="answer")
        _turn_outcome(deps, outcome="BLOCKED", reason_code="blocked_tool_failure", delivered_artifact_kind="none")
        outcome_events = [e for e in events if e[0] == "turn.outcome"]
        assert len(outcome_events) == 1
        assert outcome_events[0][1]["reason_code"] == "answered"

    def test_turn_end_has_total_cost(self, monkeypatch):
        events = []

        def _fake_log_event(event, **fields):
            events.append((event, fields))

        monkeypatch.setattr("autodebug.debug_log.log_event", _fake_log_event)
        from autodebug.cli_v2 import _turn_end

        deps = SimpleNamespace(_current_turn_id="t-2")
        _turn_end(
            deps,
            model_input_tokens=100,
            model_output_tokens=50,
            estimated_model_cost_usd=0.012,
            estimated_tool_cost_usd=0.008,
        )
        assert events[-1][0] == "turn.end"
        assert events[-1][1]["total_estimated_cost_usd"] == 0.02

    def test_turn_synthesis_attempt_emitted_once(self, monkeypatch):
        events = []

        def _fake_log_event(event, **fields):
            events.append((event, fields))

        monkeypatch.setattr("autodebug.debug_log.log_event", _fake_log_event)
        from autodebug.cli_v2 import _turn_synthesis_attempt

        deps = SimpleNamespace(_current_turn_id="t-3", _turn_synthesis_emitted=False)
        _turn_synthesis_attempt(deps, reason="budget_exhausted")
        _turn_synthesis_attempt(deps, reason="budget_exhausted")
        synth_events = [e for e in events if e[0] == "turn.synthesis_attempt"]
        assert len(synth_events) == 1

    def test_turn_lifecycle_ordering(self, monkeypatch):
        events = []

        def _fake_log_event(event, **fields):
            events.append(event)

        monkeypatch.setattr("autodebug.debug_log.log_event", _fake_log_event)
        from autodebug.cli_v2 import _turn_start, _turn_synthesis_attempt, _turn_outcome, _turn_end

        deps = SimpleNamespace(_skill_budget=1)
        _turn_start(deps, backend="pydantic-ai", prompt="scan", is_action_prompt=True)
        _turn_synthesis_attempt(deps, reason="budget_exhausted")
        _turn_outcome(deps, outcome="DELIVER", reason_code="forced_summary_budget_exhausted", delivered_artifact_kind="answer")
        _turn_end(deps, model_input_tokens=1, model_output_tokens=1, estimated_model_cost_usd=0.0, estimated_tool_cost_usd=0.0)
        assert events == [
            "turn.start",
            "turn.tool_budget",
            "turn.synthesis_attempt",
            "turn.outcome",
            "turn.end",
        ]

    def test_ollama_model_returns_none(self):
        from autodebug.cli_v2 import _force_tool_model_settings
        from autodebug.agent.core import HikaflowDeps

        deps = HikaflowDeps(
            api_base="https://x.com", token="t", project_path="/tmp",
            model_id="ollama:llama3.2",
        )
        assert _force_tool_model_settings(deps) is None

    def test_empty_model_id_returns_none(self):
        from autodebug.cli_v2 import _force_tool_model_settings
        from autodebug.agent.core import HikaflowDeps

        deps = HikaflowDeps(
            api_base="https://x.com", token="t", project_path="/tmp",
            model_id="",
        )
        assert _force_tool_model_settings(deps) is None

    def test_force_no_tool_settings_openai(self):
        from autodebug.cli_v2 import _force_no_tool_model_settings
        from autodebug.agent.core import HikaflowDeps

        deps = HikaflowDeps(
            api_base="https://x.com", token="t", project_path="/tmp",
            model_id="openai:gpt-5",
        )
        settings = _force_no_tool_model_settings(deps)
        assert settings is not None
        assert settings.get("extra_body") == {"tool_choice": "none"}

    def test_force_no_tool_settings_anthropic(self):
        from autodebug.cli_v2 import _force_no_tool_model_settings
        from autodebug.agent.core import HikaflowDeps

        deps = HikaflowDeps(
            api_base="https://x.com", token="t", project_path="/tmp",
            model_id="anthropic:claude-sonnet-4-5-20250929",
        )
        settings = _force_no_tool_model_settings(deps)
        assert settings is not None
        assert settings.get("extra_body") == {"tool_choice": {"type": "none"}}


class TestMcpStatusCommand:
    def test_show_mcp_migration_status_runs(self, monkeypatch):
        from autodebug import cli_v2

        lines = []
        monkeypatch.setattr(cli_v2, "_cprint", lambda *args, **kwargs: lines.append(" ".join(str(a) for a in args)))
        cli_v2._show_mcp_migration_status()
        assert any("MCP Migration Flags" in line for line in lines)


class TestForcedSynthesisPass:
    def test_forced_synthesis_pass_invokes_no_tool_settings(self, monkeypatch):
        from autodebug import cli_v2

        captured = {"model_settings": None, "prompt": None}

        async def _fake_run_agent_stream(agent, deps, prompt, history, **kwargs):
            captured["model_settings"] = kwargs.get("model_settings")
            captured["prompt"] = prompt
            return "final synthesis", SimpleNamespace()

        monkeypatch.setattr(cli_v2, "_run_agent_stream", _fake_run_agent_stream)
        deps = SimpleNamespace(model_id="openai:gpt-5", _current_turn_id="t1", _turn_synthesis_emitted=False)
        out, _resp = __import__("asyncio").run(
            cli_v2._run_forced_synthesis_pass(
                agent=SimpleNamespace(),
                deps=deps,
                history=[],
                headless=True,
                mode="quiet",
            )
        )
        assert out == "final synthesis"
        assert "Do not call tools" in captured["prompt"]
        assert captured["model_settings"] is not None
