"""Tests for markitect.llm.claude_code."""

import subprocess
from unittest import mock

import pytest

from markitect.llm.claude_code import ClaudeCodeAdapter
from markitect.llm.exceptions import LLMSubprocessError, LLMTimeoutError
from markitect.prompts.execution.models import RunConfig, LLMResponse


class TestClaudeCodeAdapter:
    @mock.patch("markitect.llm.claude_code.subprocess.run")
    def test_execute_prompt_success(self, mock_run):
        mock_run.return_value = subprocess.CompletedProcess(
            args=["claude", "--print"],
            returncode=0,
            stdout="Generated output text",
            stderr="",
        )
        adapter = ClaudeCodeAdapter()
        config = RunConfig()

        resp = adapter.execute_prompt("Write something", config)

        assert isinstance(resp, LLMResponse)
        assert resp.content == "Generated output text"
        assert resp.model == "claude-code-cli"
        assert resp.usage["prompt_tokens"] > 0
        assert resp.usage["completion_tokens"] > 0
        assert resp.metadata["provider"] == "claude-code"

    @mock.patch("markitect.llm.claude_code.subprocess.run")
    def test_prompt_passed_via_stdin(self, mock_run):
        mock_run.return_value = subprocess.CompletedProcess(
            args=["claude", "--print"],
            returncode=0,
            stdout="ok",
            stderr="",
        )
        adapter = ClaudeCodeAdapter()
        config = RunConfig()

        adapter.execute_prompt("My big prompt", config)

        call_kwargs = mock_run.call_args[1]
        assert call_kwargs["input"] == "My big prompt"
        assert call_kwargs["text"] is True

    @mock.patch("markitect.llm.claude_code.subprocess.run")
    def test_model_flag_passed(self, mock_run):
        mock_run.return_value = subprocess.CompletedProcess(
            args=["claude", "--print", "--model", "opus"],
            returncode=0,
            stdout="ok",
            stderr="",
        )
        adapter = ClaudeCodeAdapter(model="opus")
        config = RunConfig()

        adapter.execute_prompt("Test", config)

        cmd = mock_run.call_args[0][0]
        assert "--model" in cmd
        assert "opus" in cmd

    @mock.patch("markitect.llm.claude_code.subprocess.run")
    def test_nonzero_exit_raises(self, mock_run):
        mock_run.return_value = subprocess.CompletedProcess(
            args=["claude", "--print"],
            returncode=1,
            stdout="",
            stderr="Error: something went wrong",
        )
        adapter = ClaudeCodeAdapter()
        config = RunConfig()

        with pytest.raises(LLMSubprocessError) as exc_info:
            adapter.execute_prompt("Test", config)
        assert exc_info.value.return_code == 1
        assert "something went wrong" in exc_info.value.stderr

    @mock.patch("markitect.llm.claude_code.subprocess.run")
    def test_timeout_raises(self, mock_run):
        mock_run.side_effect = subprocess.TimeoutExpired(cmd="claude", timeout=300)
        adapter = ClaudeCodeAdapter()
        config = RunConfig(timeout_seconds=300)

        with pytest.raises(LLMTimeoutError):
            adapter.execute_prompt("Test", config)

    @mock.patch("markitect.llm.claude_code.subprocess.run")
    def test_validate_config_success(self, mock_run):
        mock_run.return_value = subprocess.CompletedProcess(
            args=["claude", "--version"],
            returncode=0,
            stdout="1.0.0",
            stderr="",
        )
        adapter = ClaudeCodeAdapter()
        assert adapter.validate_config(RunConfig()) is True

    @mock.patch("markitect.llm.claude_code.subprocess.run")
    def test_validate_config_missing_cli(self, mock_run):
        mock_run.side_effect = FileNotFoundError("claude not found")
        adapter = ClaudeCodeAdapter()
        assert adapter.validate_config(RunConfig()) is False

    @mock.patch("markitect.llm.claude_code.subprocess.run")
    def test_token_estimation(self, mock_run):
        # 20 chars -> ~5 tokens, 40 chars -> ~10 tokens
        mock_run.return_value = subprocess.CompletedProcess(
            args=["claude", "--print"],
            returncode=0,
            stdout="a" * 40,
            stderr="",
        )
        adapter = ClaudeCodeAdapter()
        config = RunConfig()

        resp = adapter.execute_prompt("b" * 20, config)
        assert resp.usage["prompt_tokens"] == 5
        assert resp.usage["completion_tokens"] == 10
