"""Tests for interactive CLI module."""

from __future__ import annotations

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

from markitai.cli.interactive import (
    InteractiveSession,
    ProviderDetectionResult,
    detect_all_llm_providers,
    detect_llm_provider,
)


class TestProviderDetection:
    """Tests for LLM provider auto-detection (via interactive re-exports)."""

    def test_detect_claude_cli_authenticated(self) -> None:
        """Should detect authenticated Claude CLI."""
        with (
            patch(
                "markitai.cli.providers_detect.shutil.which",
                side_effect=lambda cmd: "/usr/bin/claude" if cmd == "claude" else None,
            ),
            patch(
                "markitai.cli.providers_detect._check_claude_auth",
                return_value=True,
            ),
            patch(
                "markitai.cli.providers_detect._check_chatgpt_auth",
                return_value=False,
            ),
            patch(
                "markitai.cli.providers_detect._check_gemini_cli_auth",
                return_value=False,
            ),
            patch.dict("os.environ", {}, clear=True),
        ):
            result = detect_llm_provider()
            assert result is not None
            assert result.provider == "claude-agent"
            assert result.model == "claude-agent/sonnet"
            assert result.authenticated is True

    def test_detect_copilot_cli_authenticated(self) -> None:
        """Should detect authenticated Copilot CLI when Claude not available."""
        with (
            patch(
                "markitai.cli.providers_detect.shutil.which",
                side_effect=lambda x: "/usr/bin/copilot" if x == "copilot" else None,
            ),
            patch(
                "markitai.cli.providers_detect._check_copilot_auth",
                return_value=True,
            ),
            patch(
                "markitai.cli.providers_detect._check_chatgpt_auth",
                return_value=False,
            ),
            patch(
                "markitai.cli.providers_detect._check_gemini_cli_auth",
                return_value=False,
            ),
            patch.dict("os.environ", {}, clear=True),
        ):
            result = detect_llm_provider()
            assert result is not None
            assert result.provider == "copilot"
            assert result.model == "copilot/claude-sonnet-4.6"

    def test_detect_anthropic_api_key(self) -> None:
        """Should detect ANTHROPIC_API_KEY environment variable."""
        with (
            patch("markitai.cli.providers_detect.shutil.which", return_value=None),
            patch(
                "markitai.cli.providers_detect._check_chatgpt_auth",
                return_value=False,
            ),
            patch(
                "markitai.cli.providers_detect._check_gemini_cli_auth",
                return_value=False,
            ),
            patch.dict("os.environ", {"ANTHROPIC_API_KEY": "sk-test"}, clear=True),
        ):
            result = detect_llm_provider()
            assert result is not None
            assert result.provider == "anthropic"
            assert result.model == "anthropic/claude-sonnet-4-6"

    def test_detect_openai_api_key(self) -> None:
        """Should detect OPENAI_API_KEY when no other provider available."""
        with (
            patch("markitai.cli.providers_detect.shutil.which", return_value=None),
            patch(
                "markitai.cli.providers_detect._check_chatgpt_auth",
                return_value=False,
            ),
            patch(
                "markitai.cli.providers_detect._check_gemini_cli_auth",
                return_value=False,
            ),
            patch.dict("os.environ", {"OPENAI_API_KEY": "sk-test"}, clear=True),
        ):
            result = detect_llm_provider()
            assert result is not None
            assert result.provider == "openai"
            assert result.model == "openai/gpt-5.4"

    def test_detect_gemini_api_key(self) -> None:
        """Should detect GEMINI_API_KEY when no other provider available."""
        with (
            patch("markitai.cli.providers_detect.shutil.which", return_value=None),
            patch(
                "markitai.cli.providers_detect._check_chatgpt_auth",
                return_value=False,
            ),
            patch(
                "markitai.cli.providers_detect._check_gemini_cli_auth",
                return_value=False,
            ),
            patch.dict("os.environ", {"GEMINI_API_KEY": "test-key"}, clear=True),
        ):
            result = detect_llm_provider()
            assert result is not None
            assert result.provider == "gemini"
            assert result.model == "gemini/gemini-3.1-flash-lite-preview"

    def test_detect_deepseek_api_key(self) -> None:
        """Should detect DEEPSEEK_API_KEY when no higher-priority provider available."""
        with (
            patch("markitai.cli.providers_detect.shutil.which", return_value=None),
            patch(
                "markitai.cli.providers_detect._check_chatgpt_auth",
                return_value=False,
            ),
            patch(
                "markitai.cli.providers_detect._check_gemini_cli_auth",
                return_value=False,
            ),
            patch.dict("os.environ", {"DEEPSEEK_API_KEY": "sk-test"}, clear=True),
        ):
            result = detect_llm_provider()
            assert result is not None
            assert result.provider == "deepseek"
            assert result.model == "deepseek/deepseek-chat"

    def test_detect_openrouter_api_key(self) -> None:
        """Should detect OPENROUTER_API_KEY when no higher-priority provider available."""
        with (
            patch("markitai.cli.providers_detect.shutil.which", return_value=None),
            patch(
                "markitai.cli.providers_detect._check_chatgpt_auth",
                return_value=False,
            ),
            patch(
                "markitai.cli.providers_detect._check_gemini_cli_auth",
                return_value=False,
            ),
            patch.dict("os.environ", {"OPENROUTER_API_KEY": "sk-test"}, clear=True),
        ):
            result = detect_llm_provider()
            assert result is not None
            assert result.provider == "openrouter"
            assert result.model == "openrouter/google/gemini-3.1-flash-lite-preview"

    def test_detect_no_provider(self) -> None:
        """Should return None when no provider detected."""
        with (
            patch("markitai.cli.providers_detect.shutil.which", return_value=None),
            patch(
                "markitai.cli.providers_detect._check_chatgpt_auth",
                return_value=False,
            ),
            patch(
                "markitai.cli.providers_detect._check_gemini_cli_auth",
                return_value=False,
            ),
            patch.dict("os.environ", {}, clear=True),
        ):
            result = detect_llm_provider()
            assert result is None

    def test_detect_all_returns_multiple(self) -> None:
        """Should return all available providers, not just the first."""
        with (
            patch(
                "markitai.cli.providers_detect.shutil.which",
                side_effect=lambda x: "/usr/bin/claude" if x == "claude" else None,
            ),
            patch(
                "markitai.cli.providers_detect._check_claude_auth",
                return_value=True,
            ),
            patch(
                "markitai.cli.providers_detect._check_chatgpt_auth",
                return_value=False,
            ),
            patch(
                "markitai.cli.providers_detect._check_gemini_cli_auth",
                return_value=False,
            ),
            patch.dict(
                "os.environ",
                {"GEMINI_API_KEY": "test-key", "OPENAI_API_KEY": "sk-test"},
                clear=True,
            ),
        ):
            results = detect_all_llm_providers()
            assert len(results) == 3
            providers = [r.provider for r in results]
            assert providers == ["claude-agent", "openai", "gemini"]

    def test_detect_chatgpt_provider(self) -> None:
        """Should detect ChatGPT when authenticated."""
        with (
            patch("markitai.cli.providers_detect.shutil.which", return_value=None),
            patch(
                "markitai.cli.providers_detect._check_chatgpt_auth",
                return_value=True,
            ),
            patch(
                "markitai.cli.providers_detect._check_gemini_cli_auth",
                return_value=False,
            ),
            patch.dict("os.environ", {}, clear=True),
        ):
            result = detect_llm_provider()
            assert result is not None
            assert result.provider == "chatgpt"
            assert result.model == "chatgpt/gpt-5.4"

    def test_detect_gemini_cli_provider(self) -> None:
        """Should detect Gemini CLI when authenticated."""
        with (
            patch("markitai.cli.providers_detect.shutil.which", return_value=None),
            patch(
                "markitai.cli.providers_detect._check_chatgpt_auth",
                return_value=False,
            ),
            patch(
                "markitai.cli.providers_detect._check_gemini_cli_auth",
                return_value=True,
            ),
            patch.dict("os.environ", {}, clear=True),
        ):
            result = detect_llm_provider()
            assert result is not None
            assert result.provider == "gemini-cli"
            assert result.model == "gemini-cli/gemini-3.1-pro-preview"


class TestInteractiveSession:
    """Tests for InteractiveSession class."""

    def test_session_initialization(self) -> None:
        """Should initialize with default values."""
        session = InteractiveSession()
        assert session.input_path is None
        assert session.enable_llm is False
        assert session.provider_result is None


class TestInteractivePrompts:
    """Tests for interactive prompt flow."""

    @patch("questionary.select")
    def test_prompt_input_type(self, mock_select: MagicMock) -> None:
        """Should prompt for input type."""
        mock_select.return_value.ask.return_value = "directory"
        session = InteractiveSession()

        from markitai.cli.interactive import prompt_input_type

        result = prompt_input_type(session)

        assert result == "directory"
        mock_select.assert_called_once()

    @patch("questionary.path")
    def test_prompt_input_path(self, mock_path: MagicMock) -> None:
        """Should prompt for input path."""
        mock_path.return_value.ask.return_value = "./docs"
        session = InteractiveSession()
        session.input_type = "directory"

        from markitai.cli.interactive import prompt_input_path

        result = prompt_input_path(session)

        assert result == Path("./docs")

    @patch("questionary.confirm")
    def test_prompt_enable_llm(self, mock_confirm: MagicMock) -> None:
        """Should prompt for LLM enablement."""
        mock_confirm.return_value.ask.return_value = True
        session = InteractiveSession()

        from markitai.cli.interactive import prompt_enable_llm

        result = prompt_enable_llm(session)

        assert result is True


class TestConfigureProviderAutoDetect:
    """Tests for Auto-detect branch in prompt_configure_provider."""

    @patch("questionary.select")
    def test_auto_detect_with_provider_found(self, mock_select: MagicMock) -> None:
        """Auto-detect should call detect_first_provider and enable LLM when found."""
        mock_select.return_value.ask.return_value = "auto"

        from markitai.cli.interactive import prompt_configure_provider

        session = InteractiveSession()
        # Ensure no provider detected initially so we enter configure flow
        session.provider_result = None

        detected = ProviderDetectionResult(
            provider="claude-agent",
            model="claude-agent/sonnet",
            authenticated=True,
            source="cli",
        )

        with patch(
            "markitai.cli.interactive.detect_first_provider",
            return_value=detected,
        ):
            result = prompt_configure_provider(session)

        assert result is True
        assert session.provider_result is detected

    @patch("questionary.select")
    def test_auto_detect_with_no_provider_found(self, mock_select: MagicMock) -> None:
        """Auto-detect should NOT silently disable LLM when no provider found.

        Instead it should return False so the caller knows configuration failed,
        and the session.enable_llm should not be forcibly set to False here
        (that's the caller's responsibility).
        """
        mock_select.return_value.ask.return_value = "auto"

        from markitai.cli.interactive import prompt_configure_provider

        session = InteractiveSession()
        session.provider_result = None
        session.enable_llm = True  # user wanted LLM

        with patch(
            "markitai.cli.interactive.detect_first_provider",
            return_value=None,
        ):
            result = prompt_configure_provider(session)

        # Should return False (no provider found)
        assert result is False
        # Should NOT have forcibly disabled LLM (that's the caller's job)
        assert session.enable_llm is True


class TestSessionToCliArgs:
    """Tests for session_to_cli_args function."""

    def test_llm_enabled_with_alt_and_desc(self) -> None:
        """LLM enabled with alt+desc should produce correct args."""
        from markitai.cli.interactive import session_to_cli_args

        session = InteractiveSession()
        session.input_path = Path("/tmp/doc.pdf")
        session.output_dir = Path("/tmp/output")
        session.enable_llm = True
        session.enable_alt = True
        session.enable_desc = True

        args = session_to_cli_args(session)
        assert str(session.input_path) in args
        assert "-o" in args
        assert str(session.output_dir) in args
        assert "--llm" in args
        assert "--alt" in args
        assert "--desc" in args
        assert "--no-llm" not in args

    def test_llm_disabled_with_ocr_and_screenshot(self) -> None:
        """LLM disabled with OCR+screenshot should produce correct args."""
        from markitai.cli.interactive import session_to_cli_args

        session = InteractiveSession()
        session.input_path = Path("/tmp/doc.pdf")
        session.output_dir = Path("/tmp/output")
        session.enable_llm = False
        session.enable_ocr = True
        session.enable_screenshot = True

        args = session_to_cli_args(session)
        assert "--no-llm" in args
        assert "--ocr" in args
        assert "--screenshot" in args
        assert "--llm" not in args

    def test_ocr_independent_of_llm(self) -> None:
        """OCR should be present even with --no-llm."""
        from markitai.cli.interactive import session_to_cli_args

        session = InteractiveSession()
        session.input_path = Path("/tmp/doc.pdf")
        session.output_dir = Path("/tmp/output")
        session.enable_llm = False
        session.enable_ocr = True

        args = session_to_cli_args(session)
        assert "--no-llm" in args
        assert "--ocr" in args


class TestPureModeInInteractive:
    """Tests for pure mode support in interactive session."""

    def test_session_has_enable_pure_field(self) -> None:
        """InteractiveSession should have enable_pure field, default False."""
        session = InteractiveSession()
        assert session.enable_pure is False

    def test_session_to_cli_args_includes_pure(self) -> None:
        """When enable_pure is True, --pure should appear in CLI args."""
        from markitai.cli.interactive import session_to_cli_args

        session = InteractiveSession()
        session.input_path = Path("/tmp/doc.pdf")
        session.output_dir = Path("/tmp/output")
        session.enable_llm = True
        session.enable_pure = True

        args = session_to_cli_args(session)
        assert "--pure" in args
        assert "--llm" in args

    def test_session_to_cli_args_excludes_pure_when_disabled(self) -> None:
        """When enable_pure is False, --pure should NOT appear."""
        from markitai.cli.interactive import session_to_cli_args

        session = InteractiveSession()
        session.input_path = Path("/tmp/doc.pdf")
        session.output_dir = Path("/tmp/output")
        session.enable_llm = True
        session.enable_pure = False

        args = session_to_cli_args(session)
        assert "--pure" not in args

    @patch("questionary.checkbox")
    def test_prompt_llm_options_offers_pure_mode(
        self, mock_checkbox: MagicMock
    ) -> None:
        """LLM options should include pure mode as a choice."""
        mock_checkbox.return_value.ask.return_value = ["pure"]

        from markitai.cli.interactive import prompt_llm_options

        session = InteractiveSession()
        session.enable_llm = True

        prompt_llm_options(session)

        assert session.enable_pure is True
        # Verify the checkbox was called with choices that include pure
        call_args = mock_checkbox.call_args
        choice_values = [
            c.value
            for c in call_args[1].get(
                "choices", call_args[0][1] if len(call_args[0]) > 1 else []
            )
        ]
        assert "pure" in choice_values


class TestAtomicEnvWrite:
    """Tests for atomic .env file writing."""

    def test_append_env_var_uses_atomic_write(self, tmp_path: Path) -> None:
        """_append_env_var should use atomic_write_text for safe writes."""
        from markitai.cli.interactive import _append_env_var

        env_path = tmp_path / ".env"

        with patch("markitai.security.atomic_write_text") as mock_atomic:
            _append_env_var(env_path, "TEST_KEY", "test_value")
            mock_atomic.assert_called_once()
            # Verify the content would be correct
            call_args = mock_atomic.call_args
            assert call_args[0][0] == env_path
            assert 'TEST_KEY="test_value"' in call_args[0][1]

    def test_append_env_var_updates_existing_key(self, tmp_path: Path) -> None:
        """_append_env_var should update existing key atomically."""
        from markitai.cli.interactive import _append_env_var

        env_path = tmp_path / ".env"
        env_path.write_text("OLD_KEY=old\nTEST_KEY=original\n", encoding="utf-8")

        # Call should update TEST_KEY
        _append_env_var(env_path, "TEST_KEY", "new_value")

        content = env_path.read_text(encoding="utf-8")
        assert 'TEST_KEY="new_value"' in content
        assert "TEST_KEY=original" not in content

    def test_append_env_var_quotes_values(self, tmp_path: Path) -> None:
        """_append_env_var should quote values in .env files."""
        from markitai.cli.interactive import _append_env_var

        env_path = tmp_path / ".env"

        _append_env_var(env_path, "API_KEY", "sk-test-123")

        content = env_path.read_text(encoding="utf-8")
        assert 'API_KEY="sk-test-123"' in content

    def test_append_env_var_updates_quoted_existing_key(self, tmp_path: Path) -> None:
        """_append_env_var should detect and update existing quoted keys."""
        from markitai.cli.interactive import _append_env_var

        env_path = tmp_path / ".env"
        env_path.write_text('API_KEY="old-value"\n', encoding="utf-8")

        _append_env_var(env_path, "API_KEY", "new-value")

        content = env_path.read_text(encoding="utf-8")
        assert 'API_KEY="new-value"' in content
        assert "old-value" not in content


class TestRunInteractive:
    """Tests for the main run_interactive function."""

    @patch("markitai.cli.interactive.prompt_input_type")
    @patch("markitai.cli.interactive.prompt_input_path")
    @patch("markitai.cli.interactive.prompt_output_dir")
    @patch("markitai.cli.interactive.prompt_enable_llm")
    @patch("markitai.cli.interactive.prompt_llm_options")
    @patch("markitai.cli.interactive.prompt_extra_options")
    @patch("markitai.cli.interactive.prompt_configure_provider")
    def test_run_interactive_basic_flow(
        self,
        mock_configure: MagicMock,
        mock_extra: MagicMock,
        mock_options: MagicMock,
        mock_llm: MagicMock,
        mock_output: MagicMock,
        mock_path: MagicMock,
        mock_type: MagicMock,
    ) -> None:
        """Should run through all prompts in order."""
        mock_type.return_value = "file"
        mock_path.return_value = Path("test.pdf")
        mock_output.return_value = Path("./output")
        mock_llm.return_value = False

        from markitai.cli.interactive import run_interactive

        session = run_interactive()

        assert session.input_type == "file"
        mock_type.assert_called_once()
        mock_path.assert_called_once()
        mock_output.assert_called_once()
        mock_llm.assert_called_once()
