"""
Comprehensive tests for ChatContinue functionality.

Tests are written based on the public interface, not implementation details.
Tests challenge the business logic and verify correct behavior.
"""

from unittest.mock import Mock, patch

import pytest

from lexilux import Chat, ChatContinue, ChatResult
from lexilux.usage import Usage


class TestChatContinueContinueRequest:
    """Test ChatContinue.continue_request method"""

    @patch("requests.Session.post")
    def test_continue_request_with_prompt(self, mock_post):
        """Test continue_request with add_continue_prompt=True"""
        chat = Chat(
            base_url="https://api.example.com/v1",
            api_key="test-key",
            model="gpt-4",
        )

        result1 = ChatResult(
            text="This is part 1",
            usage=Usage(input_tokens=10, output_tokens=50, total_tokens=60),
            finish_reason="length",
        )

        # Build messages list (new API uses plain messages, not ChatHistory)
        messages = [
            {"role": "user", "content": "Write a story"},
            {"role": "assistant", "content": result1.text},
        ]

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "choices": [
                {"message": {"content": " and part 2"}, "finish_reason": "stop"}
            ],
            "usage": {"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30},
        }
        mock_response.raise_for_status = Mock()
        mock_post.return_value = mock_response

        continue_result = ChatContinue.continue_request(
            chat,
            result1,
            messages=messages,
            add_continue_prompt=True,
            continue_prompt="continue",
        )

        # New API with auto_merge=True (default) returns merged result
        assert continue_result.text == "This is part 1 and part 2"
        assert " and part 2" in continue_result.text
        # Verify that messages was updated with continue prompt
        assert len(messages) >= 3  # Original user + assistant + continue prompt

    @patch("requests.Session.post")
    def test_continue_request_without_prompt(self, mock_post):
        """Test continue_request with add_continue_prompt=False"""
        chat = Chat(
            base_url="https://api.example.com/v1",
            api_key="test-key",
            model="gpt-4",
        )

        result1 = ChatResult(
            text="This is part 1",
            usage=Usage(input_tokens=10, output_tokens=50, total_tokens=60),
            finish_reason="length",
        )

        # Build messages list (new API uses plain messages, not ChatHistory)
        messages = [
            {"role": "user", "content": "Write a story"},
            {"role": "assistant", "content": result1.text},
        ]

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "choices": [
                {"message": {"content": " and part 2"}, "finish_reason": "stop"}
            ],
            "usage": {"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30},
        }
        mock_response.raise_for_status = Mock()
        mock_post.return_value = mock_response

        continue_result = ChatContinue.continue_request(
            chat, result1, messages=messages, add_continue_prompt=False
        )

        # New API with auto_merge=True (default) returns merged result
        assert continue_result.text == "This is part 1 and part 2"
        assert " and part 2" in continue_result.text
        # Messages should not have additional user message when add_continue_prompt=False
        # Note: This tests the interface contract

    @patch("requests.Session.post")
    def test_continue_request_custom_prompt(self, mock_post):
        """Test continue_request with custom continue_prompt"""
        chat = Chat(
            base_url="https://api.example.com/v1",
            api_key="test-key",
            model="gpt-4",
        )

        result1 = ChatResult(
            text="This is part 1",
            usage=Usage(input_tokens=10, output_tokens=50, total_tokens=60),
            finish_reason="length",
        )

        # Build messages list (new API uses plain messages, not ChatHistory)
        messages = [
            {"role": "user", "content": "Write a story"},
            {"role": "assistant", "content": result1.text},
        ]

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "choices": [
                {"message": {"content": " continuation"}, "finish_reason": "stop"}
            ],
            "usage": {"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30},
        }
        mock_response.raise_for_status = Mock()
        mock_post.return_value = mock_response

        continue_result = ChatContinue.continue_request(
            chat,
            result1,
            messages=messages,
            add_continue_prompt=True,
            continue_prompt="Please continue",
        )

        # New API with auto_merge=True (default) returns merged result
        assert continue_result.text == "This is part 1 continuation"
        assert " continuation" in continue_result.text
        # Verify custom prompt was used - check API call payload
        assert mock_post.call_count >= 1
        # The continue request should have been made with custom prompt
        # We verify the result contains the continuation text


class TestChatContinueMergeResults:
    """Test ChatContinue.merge_results method"""

    def test_merge_results_two_results(self):
        """Test merging two results"""
        result1 = ChatResult(
            text="Part 1",
            usage=Usage(input_tokens=10, output_tokens=20, total_tokens=30),
            finish_reason="length",
        )

        result2 = ChatResult(
            text=" Part 2",
            usage=Usage(input_tokens=15, output_tokens=25, total_tokens=40),
            finish_reason="stop",
        )

        merged = ChatContinue.merge_results(result1, result2)

        assert merged.text == "Part 1 Part 2"
        assert merged.usage.total_tokens == 70  # 30 + 40
        assert merged.usage.input_tokens == 25  # 10 + 15
        assert merged.usage.output_tokens == 45  # 20 + 25
        assert merged.finish_reason == "stop"  # Should use last result's finish_reason

    def test_merge_results_single_result(self):
        """Test merging single result (edge case)"""
        result1 = ChatResult(
            text="Part 1",
            usage=Usage(input_tokens=10, output_tokens=20, total_tokens=30),
            finish_reason="stop",
        )

        merged = ChatContinue.merge_results(result1)

        assert merged.text == "Part 1"
        assert merged.usage.total_tokens == 30
        assert merged.finish_reason == "stop"

    def test_merge_results_multiple_results(self):
        """Test merging multiple results"""
        results = [
            ChatResult(
                text=f"Part {i}",
                usage=Usage(input_tokens=10, output_tokens=20, total_tokens=30),
                finish_reason="length" if i < 3 else "stop",
            )
            for i in range(1, 4)
        ]

        merged = ChatContinue.merge_results(*results)

        assert merged.text == "Part 1Part 2Part 3"
        assert merged.usage.total_tokens == 90  # 30 * 3
        assert merged.finish_reason == "stop"  # Last result's finish_reason

    def test_merge_results_with_none_usage(self):
        """Test merging results with None usage values"""
        result1 = ChatResult(
            text="Part 1",
            usage=Usage(input_tokens=None, output_tokens=20, total_tokens=30),
            finish_reason="length",
        )

        result2 = ChatResult(
            text=" Part 2",
            usage=Usage(input_tokens=15, output_tokens=None, total_tokens=40),
            finish_reason="stop",
        )

        merged = ChatContinue.merge_results(result1, result2)

        assert merged.text == "Part 1 Part 2"
        # None values should be handled gracefully
        assert merged.usage.input_tokens == 15  # Only result2 has input_tokens
        assert merged.usage.output_tokens == 20  # Only result1 has output_tokens
        assert merged.usage.total_tokens == 70  # Both have total_tokens

    def test_merge_results_empty_text(self):
        """Test merging results with empty text"""
        result1 = ChatResult(
            text="",
            usage=Usage(input_tokens=10, output_tokens=0, total_tokens=10),
            finish_reason="length",
        )

        result2 = ChatResult(
            text="Part 2",
            usage=Usage(input_tokens=15, output_tokens=25, total_tokens=40),
            finish_reason="stop",
        )

        merged = ChatContinue.merge_results(result1, result2)

        assert merged.text == "Part 2"  # Empty + "Part 2"
        assert merged.usage.total_tokens == 50

    def test_merge_results_no_results_raises_error(self):
        """Test that merge_results raises error with no results"""
        with pytest.raises(ValueError, match="At least one result is required"):
            ChatContinue.merge_results()


class TestChatContinueLoopBehavior:
    """Test continuation loop behavior beyond single-continue."""

    def test_continue_request_multiple_continues_updates_messages(self):
        """Second continue should include the previous continue assistant text in messages."""

        class DummyChat:
            def __init__(self, results: list[ChatResult]):
                self._results = results
                self.calls: list[list[dict[str, object]]] = []

            def __call__(self, messages, **_params):
                self.calls.append(list(messages))
                return self._results.pop(0)

        result1 = ChatResult(text="Part 1", usage=Usage(), finish_reason="length")
        result2 = ChatResult(text=" Part 2", usage=Usage(), finish_reason="length")
        result3 = ChatResult(text=" Part 3", usage=Usage(), finish_reason="stop")

        # Build messages list (new API uses plain messages, not ChatHistory)
        messages = [
            {"role": "user", "content": "Write a story"},
            {"role": "assistant", "content": result1.text},
        ]

        chat = DummyChat(results=[result2, result3])
        full = ChatContinue.continue_request(
            chat, result1, messages=messages, max_continues=2
        )

        assert full.text == "Part 1 Part 2 Part 3"
        assert len(chat.calls) == 2

        # First continue call sees initial assistant, plus one "continue" user prompt.
        assert any(
            m.get("content") == "Part 1" and m.get("role") == "assistant"
            for m in chat.calls[0]
        )

        # Second continue call should now include assistant from previous continue.
        assert any(
            m.get("content") == " Part 2" and m.get("role") == "assistant"
            for m in chat.calls[1]
        )

    def test_continue_request_on_error_raise_reraises_original(self):
        """on_error='raise' should re-raise the original exception, not RuntimeError."""

        class DummyChat:
            def __call__(self, _messages, **_params):
                raise RuntimeError("boom")

        result1 = ChatResult(text="Part 1", usage=Usage(), finish_reason="length")

        # Build messages list (new API uses plain messages, not ChatHistory)
        messages = [
            {"role": "user", "content": "Write a story"},
            {"role": "assistant", "content": result1.text},
        ]

        with pytest.raises(RuntimeError, match="boom"):
            ChatContinue.continue_request(
                DummyChat(), result1, messages=messages, max_continues=1
            )


class TestChatContinueIntegration:
    """Test ChatContinue integration scenarios"""

    @patch("requests.Session.post")
    def test_continue_workflow(self, mock_post):
        """Test complete continue workflow"""
        chat = Chat(
            base_url="https://api.example.com/v1",
            api_key="test-key",
            model="gpt-4",
        )

        # Initial request that gets cut off
        result1 = ChatResult(
            text="This is a long story that was cut off",
            usage=Usage(input_tokens=10, output_tokens=50, total_tokens=60),
            finish_reason="length",
        )

        # Build messages list (new API uses plain messages, not ChatHistory)
        messages = [
            {"role": "user", "content": "Write a long story"},
            {"role": "assistant", "content": result1.text},
        ]

        # Continue request
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "choices": [
                {
                    "message": {"content": " and here is the continuation"},
                    "finish_reason": "stop",
                }
            ],
            "usage": {"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30},
        }
        mock_response.raise_for_status = Mock()
        mock_post.return_value = mock_response

        # New API with auto_merge=True (default) returns merged result directly
        full_result = ChatContinue.continue_request(
            chat, result1, messages=messages, add_continue_prompt=True
        )

        assert "This is a long story that was cut off" in full_result.text
        assert " and here is the continuation" in full_result.text
        assert full_result.usage.total_tokens == 90  # 60 + 30
        assert full_result.finish_reason == "stop"
