"""Tests for conversation module LLM client injection."""

from __future__ import annotations

from typing import Any


class MockLLMClient:
    """Mock LLM client that records calls and returns canned responses."""

    def __init__(
        self,
        responses: list[str],
        context_window: int = 32000,
        max_output_tokens: int = 4096,
    ) -> None:
        self._responses = responses
        self._call_index = 0
        self.calls: list[dict[str, Any]] = []
        self._context_window = context_window
        self._max_output_tokens = max_output_tokens

    def responses_create(
        self,
        messages: list[dict[str, Any]],
        model: str,
        max_tokens: int = 4096,
        temperature: float = 0.0,
        return_usage: bool = False,
    ) -> str:
        self.calls.append({
            "messages": messages,
            "model": model,
            "max_tokens": max_tokens,
            "temperature": temperature,
        })
        response = self._responses[self._call_index % len(self._responses)]
        self._call_index += 1
        return response

    def get_model_context_window(self, model_id: str) -> int | None:
        """Return mock context window."""
        return self._context_window

    def get_model_max_output_tokens(self, model_id: str) -> int | None:
        """Return mock max output tokens."""
        return self._max_output_tokens


class TestConversationLLMClientInjection:
    """Test that conversation summarization uses injected LLM client."""

    def test_summarize_chunk_uses_injected_client(self) -> None:
        """_summarize_chunk should use the injected LLM client."""
        from rcl_rlm.conversation import _summarize_chunk

        mock_client = MockLLMClient(["Summary of the conversation."])

        result = _summarize_chunk(
            existing_summary="",
            messages=["User: Hello", "Assistant: Hi there"],
            model="test-model",
            max_summary_chars=500,
            llm_client=mock_client,
        )

        # Verify the mock was called
        assert len(mock_client.calls) == 1
        assert mock_client.calls[0]["model"] == "test-model"
        assert result == "Summary of the conversation."

    def test_summarize_messages_uses_injected_client(self) -> None:
        """_summarize_messages should thread the LLM client through."""
        from rcl_rlm.conversation import _summarize_messages

        mock_client = MockLLMClient(["Chunk 1 summary.", "Chunk 2 summary."])

        result = _summarize_messages(
            summary=None,
            messages=["User: Hello", "Assistant: Hi"],
            model="test-model",
            max_summary_chars=500,
            max_context_chars=10000,
            llm_client=mock_client,
        )

        # Verify the mock was called
        assert len(mock_client.calls) >= 1
        assert mock_client.calls[0]["model"] == "test-model"
        assert "summary" in result.lower() or result == "Chunk 1 summary."

    def test_build_conversation_context_uses_injected_client(self) -> None:
        """build_conversation_context should use injected LLM client for summarization."""
        from rcl_rlm.conversation import (
            CHARS_PER_TOKEN,
            DEFAULT_CONTEXT_WINDOW,
            DEFAULT_MAX_OUTPUT_TOKENS,
            SAFETY_MARGIN,
            build_conversation_context,
        )

        # Create messages that exceed the context window to trigger summarization
        max_tokens = DEFAULT_CONTEXT_WINDOW - DEFAULT_MAX_OUTPUT_TOKENS
        max_chars = int(max_tokens * CHARS_PER_TOKEN * SAFETY_MARGIN)

        # Generate enough messages to exceed the budget
        long_message = "x" * (max_chars // 2)
        messages = [
            {"role": "user", "text": long_message},
            {"role": "assistant", "text": long_message},
            {"role": "user", "text": "Recent question"},
        ]

        mock_client = MockLLMClient(["Summary of older messages."])

        context, updated_summary = build_conversation_context(
            messages=messages,
            summary=None,
            model="test-model",
            llm_client=mock_client,
        )

        # Verify the mock was called for summarization
        assert len(mock_client.calls) >= 1, "LLM client should be called for summarization"
        assert mock_client.calls[0]["model"] == "test-model"

    def test_build_conversation_context_without_client_still_works(self) -> None:
        """build_conversation_context should work without LLM client (no summarization needed)."""
        from rcl_rlm.conversation import build_conversation_context

        # Small messages that fit within context window
        messages = [
            {"role": "user", "text": "Hello"},
            {"role": "assistant", "text": "Hi there!"},
        ]

        context, updated_summary = build_conversation_context(
            messages=messages,
            summary=None,
            model="test-model",
            llm_client=None,  # No client - should work for small conversations
        )

        # Should return context without needing summarization
        assert context is not None
        assert "Hello" in context
        assert "Hi there" in context
        assert updated_summary is None  # No summarization occurred

    def test_context_char_budget_uses_client_model_metadata(self) -> None:
        """_context_char_budget should use client's model metadata methods."""
        from rcl_rlm.conversation import _context_char_budget, CHARS_PER_TOKEN, SAFETY_MARGIN

        # Create client with custom context window and max output tokens
        mock_client = MockLLMClient(
            responses=[],
            context_window=100000,
            max_output_tokens=10000,
        )

        budget = _context_char_budget("test-model", llm_client=mock_client)

        # Expected: (100000 - 10000) * 4 * 0.9 = 324000
        expected = int((100000 - 10000) * CHARS_PER_TOKEN * SAFETY_MARGIN)
        assert budget == expected

    def test_context_char_budget_uses_defaults_without_client(self) -> None:
        """_context_char_budget should use defaults when client returns None."""
        from rcl_rlm.conversation import (
            _context_char_budget,
            CHARS_PER_TOKEN,
            DEFAULT_CONTEXT_WINDOW,
            DEFAULT_MAX_OUTPUT_TOKENS,
            SAFETY_MARGIN,
        )

        # Create client that returns None for model metadata
        class NoneMetadataClient:
            def get_model_context_window(self, model_id: str) -> int | None:
                return None

            def get_model_max_output_tokens(self, model_id: str) -> int | None:
                return None

        mock_client = NoneMetadataClient()
        budget = _context_char_budget("test-model", llm_client=mock_client)

        # Should fall back to defaults
        expected = int((DEFAULT_CONTEXT_WINDOW - DEFAULT_MAX_OUTPUT_TOKENS) * CHARS_PER_TOKEN * SAFETY_MARGIN)
        assert budget == expected
