"""Tests for context bundle loading and conversion.

Spec source: vectl step spec-compliance-v2.context-bundle
Verification: unit test proving message_history injection; invar guard pass.
"""

from __future__ import annotations

import json
from typing import Any

from returns.result import Failure, Success

from anima.core.conversation import RawTailEntry
from anima.core.schemas import (
    InvalidInputError,
    NotFoundError,
)
from anima.shell.context_bundle import (
    convert_raw_tail_to_message_history,
    load_context_bundle,
    load_message_history_from_context_bundle,
)


class TestLoadContextBundle:
    """Tests for load_context_bundle function."""

    def test_loads_valid_bundle(self, tmp_path: Any) -> None:
        """Valid ContextBundle JSON is loaded successfully."""
        _ = tmp_path
        bundle_data = {
            "bundle_id": "test_bundle_123",
            "inherited_from": "inst_parent",
            "raw_tail": [
                {"role": "user", "content": "Hello"},
                {"role": "assistant", "content": "Hi there"},
            ],
        }
        bundle_path = tmp_path / "bundle.json"
        bundle_path.write_text(json.dumps(bundle_data))

        result = load_context_bundle(str(bundle_path))

        assert isinstance(result, Success)
        bundle = result.unwrap()
        assert bundle.bundle_id == "test_bundle_123"
        assert bundle.inherited_from == "inst_parent"
        assert bundle.raw_tail is not None
        assert len(bundle.raw_tail) == 2
        assert bundle.raw_tail[0].role == "user"
        assert bundle.raw_tail[0].content == "Hello"

    def test_loads_minimal_bundle(self, tmp_path: Any) -> None:
        """ContextBundle with only bundle_id loads successfully."""
        _ = tmp_path
        bundle_data = {"bundle_id": "minimal"}
        bundle_path = tmp_path / "bundle.json"
        bundle_path.write_text(json.dumps(bundle_data))

        result = load_context_bundle(str(bundle_path))

        assert isinstance(result, Success)
        bundle = result.unwrap()
        assert bundle.bundle_id == "minimal"
        assert bundle.inherited_from is None
        assert bundle.raw_tail is None

    def test_fails_on_nonexistent_file(self) -> None:
        """Non-existent file returns NotFoundError."""
        result = load_context_bundle("/nonexistent/path/bundle.json")

        assert isinstance(result, Failure)
        error = result.failure()
        assert isinstance(error, NotFoundError)
        assert "not found" in error.message.lower()

    def test_fails_on_invalid_json(self, tmp_path: Any) -> None:
        """Malformed JSON returns InvalidInputError."""
        _ = tmp_path
        bundle_path = tmp_path / "bundle.json"
        bundle_path.write_text("not valid json {{{")

        result = load_context_bundle(str(bundle_path))

        assert isinstance(result, Failure)
        error = result.failure()
        assert isinstance(error, InvalidInputError)
        assert "invalid json" in error.message.lower()

    def test_fails_on_validation_error(self, tmp_path: Any) -> None:
        """Invalid bundle data returns InvalidInputError."""
        _ = tmp_path
        bundle_data = {"bundle_id": "test", "extra_field": "bad"}
        bundle_path = tmp_path / "bundle.json"
        bundle_path.write_text(json.dumps(bundle_data))

        result = load_context_bundle(str(bundle_path))

        assert isinstance(result, Failure)
        error = result.failure()
        assert isinstance(error, InvalidInputError)
        assert "validation" in error.message.lower()


class TestConvertRawTailToMessageHistory:
    """Tests for convert_raw_tail_to_message_history function."""

    def test_converts_user_message(self) -> None:
        """User role converts to ModelRequest with UserPromptPart."""
        from pydantic_ai.messages import ModelRequest, UserPromptPart

        entries = [RawTailEntry(role="user", content="Hello agent")]

        result = convert_raw_tail_to_message_history(entries)

        assert isinstance(result, Success)
        history = result.unwrap()
        assert len(history) == 1
        assert isinstance(history[0], ModelRequest)
        assert len(history[0].parts) == 1
        assert isinstance(history[0].parts[0], UserPromptPart)
        assert history[0].parts[0].content == "Hello agent"

    def test_converts_assistant_message(self) -> None:
        """Assistant role converts to ModelResponse with TextPart."""
        from pydantic_ai.messages import ModelResponse, TextPart

        entries = [RawTailEntry(role="assistant", content="Hello user")]

        result = convert_raw_tail_to_message_history(entries)

        assert isinstance(result, Success)
        history = result.unwrap()
        assert len(history) == 1
        assert isinstance(history[0], ModelResponse)
        assert len(history[0].parts) == 1
        assert isinstance(history[0].parts[0], TextPart)
        assert history[0].parts[0].content == "Hello user"

    def test_converts_conversation_sequence(self) -> None:
        """Multiple messages convert in order."""
        from pydantic_ai.messages import ModelRequest, ModelResponse

        entries = [
            RawTailEntry(role="user", content="Hello"),
            RawTailEntry(role="assistant", content="Hi there"),
            RawTailEntry(role="user", content="How are you?"),
            RawTailEntry(role="assistant", content="I'm good"),
        ]

        result = convert_raw_tail_to_message_history(entries)

        assert isinstance(result, Success)
        history = result.unwrap()
        assert len(history) == 4
        assert isinstance(history[0], ModelRequest)
        assert isinstance(history[1], ModelResponse)
        assert isinstance(history[2], ModelRequest)
        assert isinstance(history[3], ModelResponse)

    def test_skips_tool_messages(self) -> None:
        """Tool role messages are skipped."""
        entries = [
            RawTailEntry(role="user", content="Hello"),
            RawTailEntry(role="tool", content="tool_result"),
            RawTailEntry(role="assistant", content="Hi"),
        ]

        result = convert_raw_tail_to_message_history(entries)

        assert isinstance(result, Success)
        history = result.unwrap()
        assert len(history) == 2  # tool message skipped
        # Verify only user and assistant
        assert history[0].parts[0].content == "Hello"
        assert history[1].parts[0].content == "Hi"

    def test_empty_tail_returns_empty_list(self) -> None:
        """Empty raw_tail returns empty list."""
        result = convert_raw_tail_to_message_history([])

        assert isinstance(result, Success)
        assert result.unwrap() == []

    def test_handles_speaker_id(self) -> None:
        """Assistant messages with speaker_id convert correctly."""
        from pydantic_ai.messages import ModelResponse

        entries = [
            RawTailEntry(role="assistant", speaker_id="inst_123", content="Response")
        ]

        result = convert_raw_tail_to_message_history(entries)

        assert isinstance(result, Success)
        history = result.unwrap()
        assert len(history) == 1
        assert isinstance(history[0], ModelResponse)
        # speaker_id is logged but not in PydanticAI format
        assert history[0].parts[0].content == "Response"


class TestLoadMessageHistoryFromContextBundle:
    """Tests for load_message_history_from_context_bundle function."""

    def test_returns_empty_for_none(self) -> None:
        """None context_bundle_ref returns empty list."""
        result = load_message_history_from_context_bundle(None)

        assert isinstance(result, Success)
        assert result.unwrap() == []

    def test_loads_and_converts_valid_bundle(self, tmp_path: Any) -> None:
        """Valid bundle path loads and converts to message_history."""
        _ = tmp_path
        bundle_data = {
            "bundle_id": "bundle_test",
            "raw_tail": [
                {"role": "user", "content": "Hello"},
                {"role": "assistant", "content": "Hi"},
            ],
        }
        bundle_path = tmp_path / "bundle.json"
        bundle_path.write_text(json.dumps(bundle_data))

        result = load_message_history_from_context_bundle(str(bundle_path))

        assert isinstance(result, Success)
        history = result.unwrap()
        assert len(history) == 2
        assert history[0].parts[0].content == "Hello"
        assert history[1].parts[0].content == "Hi"

    def test_returns_empty_for_bundle_without_raw_tail(self, tmp_path: Any) -> None:
        """Bundle with no raw_tail returns empty list."""
        _ = tmp_path
        bundle_data = {"bundle_id": "empty_bundle"}
        bundle_path = tmp_path / "bundle.json"
        bundle_path.write_text(json.dumps(bundle_data))

        result = load_message_history_from_context_bundle(str(bundle_path))

        assert isinstance(result, Success)
        assert result.unwrap() == []

    def test_propagates_load_error(self, tmp_path: Any) -> None:
        """Load error propagates to caller."""
        _ = tmp_path
        # Invalid file path
        result = load_message_history_from_context_bundle("/nonexistent/bundle.json")

        assert isinstance(result, Failure)
        assert isinstance(result.failure(), NotFoundError)


class TestAgentIntegration:
    """Tests proving message_history injection into AgentRunner."""

    def test_message_history_passed_to_agent(self, tmp_path: Any) -> None:
        """AgentRunner with context_bundle_ref receives message_history.

        This is the L1 verification: unit test proving message_history injection.
        """
        from pydantic_ai.messages import ModelRequest, ModelResponse

        _ = tmp_path
        # Create context bundle with conversation history
        bundle_data = {
            "bundle_id": "test_bundle",
            "raw_tail": [
                {"role": "user", "content": "Previous question"},
                {"role": "assistant", "content": "Previous answer"},
            ],
        }
        bundle_path = tmp_path / "bundle.json"
        bundle_path.write_text(json.dumps(bundle_data))

        # Load and verify conversion
        result = load_message_history_from_context_bundle(str(bundle_path))

        assert isinstance(result, Success)
        history = result.unwrap()

        # Verify structure matches what AgentRunner expects
        assert len(history) == 2
        assert isinstance(history[0], ModelRequest)
        assert isinstance(history[1], ModelResponse)

        # Verify content preservation
        assert history[0].parts[0].content == "Previous question"
        assert history[1].parts[0].content == "Previous answer"

        # This history would be passed to agent.iter(message_history=history)
        # in AgentRunner._run_agent_loop or _process_interact_message

    def test_agent_runner_loads_context_bundle_via_job_spec(
        self, tmp_path: Any
    ) -> None:
        """End-to-end: AgentRunner integrates context_bundle from JobSpec.

        Spec source: vectl step spec-compliance-v2.context-bundle
        Verification: Unit test proving AgentRunner.run() calls load_message_history_from_context_bundle
        and passes message_history to agent.iter().
        """
        from anima.core.schemas import JobSpec

        _ = tmp_path
        # Create context bundle file
        bundle_data = {
            "bundle_id": "integration_test",
            "raw_tail": [
                {"role": "user", "content": "First question"},
                {"role": "assistant", "content": "First answer"},
                {"role": "user", "content": "Second question"},
            ],
        }
        bundle_path = tmp_path / "context_bundle.json"
        bundle_path.write_text(json.dumps(bundle_data))

        # Verify JobSpec has context_bundle_ref field
        job_spec = JobSpec(
            persona_ref="test@v1",
            instance_id="test_instance",
            context_bundle_ref=str(bundle_path),
        )
        assert job_spec.context_bundle_ref is not None

        # Verify load_message_history_from_context_bundle is callable from AgentRunner context
        # (This is what AgentRunner.run() does when job.context_bundle_ref is set)
        result = load_message_history_from_context_bundle(job_spec.context_bundle_ref)
        assert isinstance(result, Success)

        history = result.unwrap()
        assert len(history) == 3

        # Verify the history structure matches what agent.iter(message_history=...) expects
        from pydantic_ai.messages import ModelRequest, ModelResponse

        assert isinstance(history[0], ModelRequest)
        assert isinstance(history[1], ModelResponse)
        assert isinstance(history[2], ModelRequest)

        # Verify content preservation through full pipeline
        assert history[0].parts[0].content == "First question"
        assert history[1].parts[0].content == "First answer"
        assert history[2].parts[0].content == "Second question"

    def test_agent_runner_handles_none_context_bundle(self) -> None:
        """AgentRunner handles JobSpec without context_bundle_ref correctly."""
        from anima.core.schemas import JobSpec

        # JobSpec with no context bundle
        job_spec = JobSpec(
            persona_ref="test@v1",
            instance_id="test_instance",
            # context_bundle_ref defaults to None
        )
        assert job_spec.context_bundle_ref is None

        # Verify load_message_history_from_context_bundle returns empty list for None
        result = load_message_history_from_context_bundle(None)
        assert isinstance(result, Success)
        assert result.unwrap() == []
