"""Tests for CLI commands."""

import json
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest
from click.testing import CliRunner

from context_manager.cli import main
from models import ContextEntry, TodoListSnapshot


@pytest.fixture
def cli_runner() -> CliRunner:
    """Create a CLI runner for testing."""
    return CliRunner()


@pytest.fixture
def mock_storage(temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> MagicMock:
    """Mock storage for CLI tests."""
    monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
    return MagicMock()


class TestContextCommands:
    """Test context CLI commands."""

    def test_context_save_with_content(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test saving context with inline content."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        result = cli_runner.invoke(
            main,
            [
                "context",
                "save",
                "--type",
                "code",
                "--title",
                "Test Context",
                "--content",
                "def hello(): pass",
                "--tags",
                "python,test",
            ],
        )

        assert result.exit_code == 0
        assert "Context saved" in result.output
        assert "ID:" in result.output

    def test_context_save_with_file(
        self, cli_runner: CliRunner, temp_db_path: str, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Test saving context from file."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        # Create temporary file
        test_file = tmp_path / "test.py"
        test_file.write_text("def test(): pass")

        result = cli_runner.invoke(
            main,
            ["context", "save", "--type", "code", "--title", "From File", "--file", str(test_file)],
        )

        assert result.exit_code == 0
        assert "Context saved" in result.output

    def test_context_save_missing_content(self, cli_runner: CliRunner) -> None:
        """Test saving context without content or file."""
        result = cli_runner.invoke(
            main,
            ["context", "save", "--type", "code", "--title", "No Content"],
        )

        assert result.exit_code == 1
        assert "Either --content or --file must be provided" in result.output

    def test_context_list(
        self, cli_runner: CliRunner, temp_db_path: str, sample_context: ContextEntry, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Test listing contexts."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        # First save a context
        from context_manager.storage import ContextStorage

        storage = ContextStorage(temp_db_path)
        storage.save_context(sample_context)

        result = cli_runner.invoke(main, ["context", "list"])

        assert result.exit_code == 0
        assert "Test Context" in result.output

    def test_context_list_with_type_filter(
        self, cli_runner: CliRunner, temp_db_path: str, sample_context: ContextEntry, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Test listing contexts with type filter."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        from context_manager.storage import ContextStorage

        storage = ContextStorage(temp_db_path)
        storage.save_context(sample_context)

        result = cli_runner.invoke(main, ["context", "list", "--type", "code"])

        assert result.exit_code == 0

    def test_context_search(
        self, cli_runner: CliRunner, temp_db_path: str, sample_context: ContextEntry, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Test searching contexts."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        from context_manager.storage import ContextStorage

        storage = ContextStorage(temp_db_path)
        storage.save_context(sample_context)

        result = cli_runner.invoke(main, ["context", "search", "test"])

        assert result.exit_code == 0

    def test_context_show(
        self, cli_runner: CliRunner, temp_db_path: str, sample_context: ContextEntry, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Test showing a specific context."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        from context_manager.storage import ContextStorage

        storage = ContextStorage(temp_db_path)
        storage.save_context(sample_context)

        result = cli_runner.invoke(main, ["context", "show", sample_context.id])

        assert result.exit_code == 0
        assert "Test Context" in result.output

    def test_context_show_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test showing a non-existent context."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        result = cli_runner.invoke(main, ["context", "show", "nonexistent-id"])

        assert result.exit_code == 1
        assert "not found" in result.output

    def test_context_show_output(
        self, cli_runner: CliRunner, temp_db_path: str, sample_context: ContextEntry, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Test showing context output format."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        from context_manager.storage import ContextStorage

        storage = ContextStorage(temp_db_path)
        storage.save_context(sample_context)

        result = cli_runner.invoke(main, ["context", "show", sample_context.id])

        assert result.exit_code == 0
        assert "Test Context" in result.output
        assert "Type:" in result.output

    def test_context_delete(
        self, cli_runner: CliRunner, temp_db_path: str, sample_context: ContextEntry, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Test deleting a context."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        from context_manager.storage import ContextStorage

        storage = ContextStorage(temp_db_path)
        storage.save_context(sample_context)

        result = cli_runner.invoke(main, ["context", "delete", sample_context.id], input="y\n")

        assert result.exit_code == 0
        assert "deleted" in result.output

    def test_context_delete_cancelled(
        self, cli_runner: CliRunner, temp_db_path: str, sample_context: ContextEntry, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Test cancelling context deletion."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        from context_manager.storage import ContextStorage

        storage = ContextStorage(temp_db_path)
        storage.save_context(sample_context)

        result = cli_runner.invoke(main, ["context", "delete", sample_context.id], input="n\n")

        assert result.exit_code == 0
        assert "Cancelled" in result.output

    @patch("context_manager.cli.ChatGPTClient")
    def test_context_save_and_query(
        self,
        mock_chatgpt_class: MagicMock,
        cli_runner: CliRunner,
        temp_db_path: str,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Test save-and-query command."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        # Mock ChatGPT client
        mock_client = MagicMock()
        mock_client.get_second_opinion = MagicMock(return_value="Mocked response")
        mock_chatgpt_class.return_value = mock_client

        result = cli_runner.invoke(
            main,
            [
                "context",
                "save-and-query",
                "--type",
                "code",
                "--title",
                "Test",
                "--content",
                "test content",
            ],
        )

        assert result.exit_code == 0
        assert "Context saved" in result.output

    @patch("context_manager.cli.ChatGPTClient")
    def test_context_ask_chatgpt(
        self,
        mock_chatgpt_class: MagicMock,
        cli_runner: CliRunner,
        temp_db_path: str,
        sample_context: ContextEntry,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Test ask-chatgpt command."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        # Save context first
        from context_manager.storage import ContextStorage

        storage = ContextStorage(temp_db_path)
        storage.save_context(sample_context)

        # Mock ChatGPT client
        mock_client = MagicMock()
        mock_client.query_context = MagicMock(return_value="Mocked response")
        mock_chatgpt_class.return_value = mock_client

        result = cli_runner.invoke(
            main,
            ["context", "ask-chatgpt", sample_context.id, "--question", "What is this?"],
        )

        assert result.exit_code == 0

    @patch("context_manager.anthropic_client.ClaudeClient")
    def test_context_ask_claude(
        self,
        mock_claude_class: MagicMock,
        cli_runner: CliRunner,
        temp_db_path: str,
        sample_context: ContextEntry,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Test ask-claude command."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        # Save context first
        from context_manager.storage import ContextStorage

        storage = ContextStorage(temp_db_path)
        storage.save_context(sample_context)

        # Mock Claude client
        mock_client = MagicMock()
        mock_client.get_second_opinion = MagicMock(return_value="Mocked response")
        mock_claude_class.return_value = mock_client

        result = cli_runner.invoke(
            main,
            ["context", "ask-claude", sample_context.id, "--question", "What is this?"],
        )

        assert result.exit_code == 0

    @patch("context_manager.gemini_client.genai.configure")
    @patch("context_manager.gemini_client.genai.GenerativeModel")
    def test_context_ask_gemini(
        self,
        mock_model: MagicMock,
        mock_configure: MagicMock,
        cli_runner: CliRunner,
        temp_db_path: str,
        sample_context: ContextEntry,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Test ask-gemini command."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
        monkeypatch.setenv("GOOGLE_API_KEY", "test-key")

        # Save context first
        from context_manager.storage import ContextStorage

        storage = ContextStorage(temp_db_path)
        storage.save_context(sample_context)

        # Mock Gemini client
        mock_instance = MagicMock()
        mock_response = MagicMock()
        mock_response.text = "Mocked Gemini response"
        mock_instance.generate_content.return_value = mock_response
        mock_model.return_value = mock_instance

        result = cli_runner.invoke(
            main,
            ["context", "ask-gemini", sample_context.id, "--question", "What is this?"],
        )

        assert result.exit_code == 0

    @patch("context_manager.deepseek_client.OpenAI")
    def test_context_ask_deepseek(
        self,
        mock_openai: MagicMock,
        cli_runner: CliRunner,
        temp_db_path: str,
        sample_context: ContextEntry,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Test ask-deepseek command."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
        monkeypatch.setenv("DEEPSEEK_API_KEY", "test-key")

        # Save context first
        from context_manager.storage import ContextStorage

        storage = ContextStorage(temp_db_path)
        storage.save_context(sample_context)

        # Mock DeepSeek client
        mock_client = MagicMock()
        mock_response = MagicMock()
        mock_response.choices = [MagicMock()]
        mock_response.choices[0].message.content = "Mocked DeepSeek response"
        mock_client.chat.completions.create.return_value = mock_response
        mock_openai.return_value = mock_client

        result = cli_runner.invoke(
            main,
            ["context", "ask-deepseek", sample_context.id, "--question", "What is this?"],
        )

        assert result.exit_code == 0


class TestTodoCommands:
    """Test todo CLI commands."""

    def test_todo_save(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test saving todos."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        # Create todos JSON
        todos_data = [
            {"content": "Task 1", "status": "pending", "activeForm": "Doing task 1"},
            {"content": "Task 2", "status": "in_progress", "activeForm": "Doing task 2"},
        ]

        result = cli_runner.invoke(
            main,
            ["todo", "save", "--todos", json.dumps(todos_data), "--context", "Test todos"],
        )

        assert result.exit_code == 0
        assert "saved" in result.output

    def test_todo_save_missing_file(self, cli_runner: CliRunner) -> None:
        """Test saving todos without file."""
        result = cli_runner.invoke(main, ["todo", "save"])

        assert result.exit_code != 0

    def test_todo_list(
        self,
        cli_runner: CliRunner,
        temp_db_path: str,
        sample_todo_snapshot: TodoListSnapshot,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Test listing todo snapshots."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        from context_manager.storage import ContextStorage

        storage = ContextStorage(temp_db_path)
        storage.save_todo_snapshot(sample_todo_snapshot)

        result = cli_runner.invoke(main, ["todo", "list"])

        assert result.exit_code == 0
        assert "snapshots" in result.output or "Task" in result.output

    def test_todo_show(
        self,
        cli_runner: CliRunner,
        temp_db_path: str,
        sample_todo_snapshot: TodoListSnapshot,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Test showing a todo snapshot."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        from context_manager.storage import ContextStorage

        storage = ContextStorage(temp_db_path)
        storage.save_todo_snapshot(sample_todo_snapshot)

        result = cli_runner.invoke(main, ["todo", "show", sample_todo_snapshot.id])

        assert result.exit_code == 0
        assert "Task 1" in result.output

    def test_todo_show_output(
        self,
        cli_runner: CliRunner,
        temp_db_path: str,
        sample_todo_snapshot: TodoListSnapshot,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Test showing todo snapshot output format."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        from context_manager.storage import ContextStorage

        storage = ContextStorage(temp_db_path)
        storage.save_todo_snapshot(sample_todo_snapshot)

        result = cli_runner.invoke(main, ["todo", "show", sample_todo_snapshot.id])

        assert result.exit_code == 0
        assert "Task 1" in result.output
        assert "Snapshot ID" in result.output

    def test_todo_restore(
        self,
        cli_runner: CliRunner,
        temp_db_path: str,
        sample_todo_snapshot: TodoListSnapshot,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Test restoring a todo snapshot."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        from context_manager.storage import ContextStorage

        storage = ContextStorage(temp_db_path)
        storage.save_todo_snapshot(sample_todo_snapshot)

        result = cli_runner.invoke(main, ["todo", "restore", sample_todo_snapshot.id])

        assert result.exit_code == 0
        assert "Task 1" in result.output

    def test_todo_search(
        self,
        cli_runner: CliRunner,
        temp_db_path: str,
        sample_todo_snapshot: TodoListSnapshot,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Test searching todo snapshots."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        from context_manager.storage import ContextStorage

        storage = ContextStorage(temp_db_path)
        storage.save_todo_snapshot(sample_todo_snapshot)

        result = cli_runner.invoke(main, ["todo", "search", "Task"])

        assert result.exit_code == 0

    def test_todo_delete(
        self,
        cli_runner: CliRunner,
        temp_db_path: str,
        sample_todo_snapshot: TodoListSnapshot,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Test deleting a todo snapshot."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        from context_manager.storage import ContextStorage

        storage = ContextStorage(temp_db_path)
        storage.save_todo_snapshot(sample_todo_snapshot)

        result = cli_runner.invoke(main, ["todo", "delete", sample_todo_snapshot.id], input="y\n")

        assert result.exit_code == 0
        assert "deleted" in result.output

    def test_todo_delete_cancelled(
        self,
        cli_runner: CliRunner,
        temp_db_path: str,
        sample_todo_snapshot: TodoListSnapshot,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Test cancelling todo deletion."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        from context_manager.storage import ContextStorage

        storage = ContextStorage(temp_db_path)
        storage.save_todo_snapshot(sample_todo_snapshot)

        result = cli_runner.invoke(main, ["todo", "delete", sample_todo_snapshot.id], input="n\n")

        assert result.exit_code == 0
        assert "Cancelled" in result.output

    def test_todo_save_invalid_json(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test saving todos with invalid JSON."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        result = cli_runner.invoke(main, ["todo", "save", "--todos", "not-valid-json"])

        assert result.exit_code == 1
        assert "Invalid todos JSON" in result.output

    def test_todo_show_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test showing non-existent todo snapshot."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        result = cli_runner.invoke(main, ["todo", "show", "nonexistent-id"])

        assert result.exit_code == 1
        assert "not found" in result.output

    def test_todo_delete_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test deleting non-existent todo snapshot."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        result = cli_runner.invoke(main, ["todo", "delete", "nonexistent-id"])

        assert result.exit_code == 1

    def test_context_list_empty(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test listing contexts when empty."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        result = cli_runner.invoke(main, ["context", "list"])

        assert result.exit_code == 0

    def test_context_search_no_results(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test searching contexts with no results."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        result = cli_runner.invoke(main, ["context", "search", "nonexistent"])

        assert result.exit_code == 0
        assert "No contexts found" in result.output

    def test_todo_search_no_results(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test searching todos with no results."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        result = cli_runner.invoke(main, ["todo", "search", "nonexistent"])

        assert result.exit_code == 0
        assert "No todo snapshots found" in result.output

    def test_todo_restore_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test restoring non-existent todo snapshot."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        result = cli_runner.invoke(main, ["todo", "restore", "nonexistent-id"])

        assert result.exit_code == 1

    @patch("context_manager.cli.ChatGPTClient")
    def test_context_save_and_query_with_file(
        self, mock_client: MagicMock, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
    ) -> None:
        """Test save-and-query with file path."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
        monkeypatch.setenv("OPENAI_API_KEY", "test-key")

        # Create temp file
        content_file = tmp_path / "content.txt"
        content_file.write_text("def test(): pass")

        # Mock ChatGPT response
        mock_instance = MagicMock()
        mock_instance.get_second_opinion.return_value = "Looks good"
        mock_client.return_value = mock_instance

        result = cli_runner.invoke(
            main,
            [
                "context",
                "save-and-query",
                "--type",
                "code",
                "--title",
                "Test",
                "--file",
                str(content_file),
            ],
        )

        assert result.exit_code == 0
        assert "Looks good" in result.output

    def test_context_save_and_query_missing_content(
        self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Test save-and-query without content or file."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        result = cli_runner.invoke(
            main,
            ["context", "save-and-query", "--type", "code", "--title", "Test"],
        )

        assert result.exit_code == 1
        assert "Either --content or --file must be provided" in result.output

    @patch("context_manager.cli.ChatGPTClient")
    def test_context_save_and_query_chatgpt_error(
        self, mock_client: MagicMock, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Test save-and-query with ChatGPT error."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
        monkeypatch.setenv("OPENAI_API_KEY", "test-key")

        # Mock ChatGPT error
        mock_instance = MagicMock()
        mock_instance.get_second_opinion.side_effect = Exception("API error")
        mock_client.return_value = mock_instance

        result = cli_runner.invoke(
            main,
            [
                "context",
                "save-and-query",
                "--type",
                "code",
                "--title",
                "Test",
                "--content",
                "test code",
            ],
        )

        assert result.exit_code == 1
        assert "Error querying ChatGPT" in result.output

    @patch("context_manager.anthropic_client.ClaudeClient")
    def test_context_ask_claude_error(
        self, mock_client: MagicMock, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Test ask-claude with Claude error."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
        monkeypatch.setenv("ANTHROPIC_API_KEY", "test-key")

        # First save a context
        result = cli_runner.invoke(
            main,
            ["context", "save", "--type", "code", "--title", "Test", "--content", "test"],
        )
        # Extract context ID (remove trailing parenthesis)
        context_id = result.output.split("ID: ")[1].split(")")[0].strip()

        # Mock Claude error
        mock_instance = MagicMock()
        mock_instance.get_second_opinion.side_effect = Exception("API error")
        mock_client.return_value = mock_instance

        result = cli_runner.invoke(main, ["context", "ask-claude", context_id])

        assert result.exit_code == 1
        assert "Error querying Claude" in result.output


class TestErrorHandling:
    """Test error handling in CLI commands."""

    def test_ask_chatgpt_context_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test ask-chatgpt with non-existent context."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        result = cli_runner.invoke(main, ["context", "ask-chatgpt", "nonexistent-id"])

        assert result.exit_code == 1
        assert "Context nonexistent-id not found" in result.output

    def test_ask_chatgpt_with_question(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test ask-chatgpt with custom question."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        # First save a context and extract ID from save output
        save_result = cli_runner.invoke(main, ["context", "save", "--type", "code", "--title", "Test", "--content", "test code"])
        # Extract ID from save output - format: "Context saved (ID: <uuid>)"
        import re

        match = re.search(r"ID: ([a-f0-9-]+)", save_result.output)
        assert match, f"Could not find context ID in: {save_result.output}"
        context_id = match.group(1)

        with patch("context_manager.cli.ChatGPTClient") as mock_client:
            mock_instance = MagicMock()
            mock_instance.get_second_opinion.return_value = "Custom answer"
            mock_client.return_value = mock_instance

            result = cli_runner.invoke(main, ["context", "ask-chatgpt", context_id, "--question", "What is this?"])

            assert result.exit_code == 0
            assert "Custom answer" in result.output
            assert "Response saved" not in result.output  # Should not save with custom question

    def test_ask_gemini_context_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test ask-gemini with non-existent context."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        result = cli_runner.invoke(main, ["context", "ask-gemini", "nonexistent-id"])

        assert result.exit_code == 1
        assert "Context nonexistent-id not found" in result.output

    def test_ask_deepseek_context_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test ask-deepseek with non-existent context."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        result = cli_runner.invoke(main, ["context", "ask-deepseek", "nonexistent-id"])

        assert result.exit_code == 1
        assert "Context nonexistent-id not found" in result.output

    def test_context_delete_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test deleting non-existent context."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        result = cli_runner.invoke(main, ["context", "delete", "nonexistent-id"])

        assert result.exit_code == 1
        assert "Context nonexistent-id not found" in result.output

    def test_context_show_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test showing non-existent context."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        result = cli_runner.invoke(main, ["context", "show", "nonexistent-id"])

        assert result.exit_code == 1
        assert "Context nonexistent-id not found" in result.output

    def test_context_search_no_results(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test search with no matching results."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        result = cli_runner.invoke(main, ["context", "search", "nonexistentquery"])

        assert result.exit_code == 0
        assert "No contexts found" in result.output

    def test_todo_restore_no_snapshot(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test restoring todo when no snapshot exists."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        result = cli_runner.invoke(main, ["todo", "restore"])

        assert result.exit_code == 1
        assert "No active todo snapshot found" in result.output

    @patch("context_manager.cli.ChatGPTClient")
    def test_ask_chatgpt_without_question(
        self, mock_client: MagicMock, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Test ask-chatgpt without custom question (second opinion)."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        # First save a context
        save_result = cli_runner.invoke(main, ["context", "save", "--type", "code", "--title", "Test", "--content", "test code"])
        import re

        match = re.search(r"ID: ([a-f0-9-]+)", save_result.output)
        assert match
        context_id = match.group(1)

        # Mock ChatGPT response
        mock_instance = MagicMock()
        mock_instance.get_second_opinion.return_value = "This looks good"
        mock_client.return_value = mock_instance

        result = cli_runner.invoke(main, ["context", "ask-chatgpt", context_id])

        assert result.exit_code == 0
        assert "This looks good" in result.output
        assert "Response saved" in result.output  # Should save when no custom question

    @patch("context_manager.anthropic_client.ClaudeClient")
    def test_ask_claude_without_question(
        self, mock_client: MagicMock, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Test ask-claude without custom question (second opinion)."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        # First save a context
        save_result = cli_runner.invoke(main, ["context", "save", "--type", "code", "--title", "Test", "--content", "test code"])
        import re

        match = re.search(r"ID: ([a-f0-9-]+)", save_result.output)
        assert match
        context_id = match.group(1)

        # Mock Claude response
        mock_instance = MagicMock()
        mock_instance.get_second_opinion.return_value = "Claude thinks this is fine"
        mock_client.return_value = mock_instance

        result = cli_runner.invoke(main, ["context", "ask-claude", context_id])

        assert result.exit_code == 0
        assert "Claude thinks this is fine" in result.output
        assert "Response saved" in result.output

    def test_ask_claude_context_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
        """Test ask-claude with non-existent context."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)

        result = cli_runner.invoke(main, ["context", "ask-claude", "nonexistent-id"])

        assert result.exit_code == 1
        assert "Context nonexistent-id not found" in result.output

    @patch("context_manager.gemini_client.genai.configure")
    @patch("context_manager.gemini_client.genai.GenerativeModel")
    def test_ask_gemini_without_question(
        self,
        mock_model: MagicMock,
        mock_configure: MagicMock,
        cli_runner: CliRunner,
        temp_db_path: str,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Test ask-gemini without custom question (second opinion)."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
        monkeypatch.setenv("GOOGLE_API_KEY", "test-key")

        # First save a context
        save_result = cli_runner.invoke(main, ["context", "save", "--type", "code", "--title", "Test", "--content", "test code"])
        import re

        match = re.search(r"ID: ([a-f0-9-]+)", save_result.output)
        assert match
        context_id = match.group(1)

        # Mock Gemini response
        mock_instance = MagicMock()
        mock_response = MagicMock()
        mock_response.text = "Gemini analysis looks good"
        mock_instance.generate_content.return_value = mock_response
        mock_model.return_value = mock_instance

        result = cli_runner.invoke(main, ["context", "ask-gemini", context_id])

        assert result.exit_code == 0
        assert "Gemini analysis looks good" in result.output
        assert "Response saved" in result.output

    @patch("context_manager.gemini_client.genai.configure")
    @patch("context_manager.gemini_client.genai.GenerativeModel")
    def test_ask_gemini_error(
        self,
        mock_model: MagicMock,
        mock_configure: MagicMock,
        cli_runner: CliRunner,
        temp_db_path: str,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Test ask-gemini with API error."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
        monkeypatch.setenv("GOOGLE_API_KEY", "test-key")

        # First save a context
        save_result = cli_runner.invoke(main, ["context", "save", "--type", "code", "--title", "Test", "--content", "test code"])
        import re

        match = re.search(r"ID: ([a-f0-9-]+)", save_result.output)
        assert match
        context_id = match.group(1)

        # Mock Gemini error
        mock_instance = MagicMock()
        mock_instance.generate_content.side_effect = Exception("API error")
        mock_model.return_value = mock_instance

        result = cli_runner.invoke(main, ["context", "ask-gemini", context_id])

        assert result.exit_code == 1
        assert "Error querying Gemini" in result.output

    @patch("context_manager.deepseek_client.OpenAI")
    def test_ask_deepseek_without_question(
        self, mock_openai: MagicMock, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Test ask-deepseek without custom question (second opinion)."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
        monkeypatch.setenv("DEEPSEEK_API_KEY", "test-key")

        # First save a context
        save_result = cli_runner.invoke(main, ["context", "save", "--type", "code", "--title", "Test", "--content", "test code"])
        import re

        match = re.search(r"ID: ([a-f0-9-]+)", save_result.output)
        assert match
        context_id = match.group(1)

        # Mock DeepSeek response
        mock_client = MagicMock()
        mock_response = MagicMock()
        mock_response.choices = [MagicMock()]
        mock_response.choices[0].message.content = "DeepSeek analysis complete"
        mock_client.chat.completions.create.return_value = mock_response
        mock_openai.return_value = mock_client

        result = cli_runner.invoke(main, ["context", "ask-deepseek", context_id])

        assert result.exit_code == 0
        assert "DeepSeek analysis complete" in result.output
        assert "Response saved" in result.output

    @patch("context_manager.deepseek_client.OpenAI")
    def test_ask_deepseek_error(
        self, mock_openai: MagicMock, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Test ask-deepseek with API error."""
        monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
        monkeypatch.setenv("DEEPSEEK_API_KEY", "test-key")

        # First save a context
        save_result = cli_runner.invoke(main, ["context", "save", "--type", "code", "--title", "Test", "--content", "test code"])
        import re

        match = re.search(r"ID: ([a-f0-9-]+)", save_result.output)
        assert match
        context_id = match.group(1)

        # Mock DeepSeek error
        mock_client = MagicMock()
        mock_client.chat.completions.create.side_effect = Exception("API error")
        mock_openai.return_value = mock_client

        result = cli_runner.invoke(main, ["context", "ask-deepseek", context_id])

        assert result.exit_code == 1
        assert "Error querying DeepSeek" in result.output
