"""Message and session fixtures for anima-owned conversation types.

Spec refs:
- docs/proposals/execution-architecture-refactor.md#tests-required/session-runtime-tests
- docs/proposals/execution-architecture-refactor.md#wave-4-schema-and-core-cleanup

Purpose:
These fixtures support testing prune/compaction operations on anima-owned
message/session types before the core/schemas/models.py split in Wave 4.

Test-only constraint:
- Do not implement package moves beyond minimal fixtures/helpers

NOTE: This file imports directly from underlying modules to avoid circular
imports during barrel import testing. The test_schema_package_cleanup.py
module validates that barrel imports work correctly.
"""

from __future__ import annotations

from datetime import datetime, timezone
from typing import Any, Literal

from pydantic_ai.messages import (
    ModelRequest,
    ModelResponse,
    TextPart,
    ToolReturnPart,
    UserPromptPart,
)

# Import from underlying modules to avoid circular import during barrel import testing
from anima.core.schemas.models import (
    CompactionConfig,
    ContextBundle,
    RawTailEntry,
    SummaryRef,
)


# =============================================================================
# SECTION 1: Type Definitions (local to avoid circular imports)
# =============================================================================


# Conversation role type - matches core.conversation.contracts.ConversationRole
ConversationRole = Literal["user", "assistant", "tool"]


# =============================================================================
# SECTION 2: PydanticAI Message Fixtures
# =============================================================================


def make_user_message(content: str) -> ModelRequest:
    """Create a PydanticAI user message for testing.

    Args:
        content: User message content.

    Returns:
        ModelRequest with a UserPromptPart.
    """
    return ModelRequest(parts=[UserPromptPart(content=content)])


def make_assistant_message(content: str) -> ModelResponse:
    """Create a PydanticAI assistant message for testing.

    Args:
        content: Assistant message content.

    Returns:
        ModelResponse with a TextPart.
    """
    return ModelResponse(parts=[TextPart(content=content)])


def make_tool_result_message(
    content: str,
    tool_name: str = "test_tool",
    tool_call_id: str = "call_1",
) -> ModelRequest:
    """Create a PydanticAI message with a tool result for testing.

    Args:
        content: Tool result content.
        tool_name: Name of the tool that produced the result.
        tool_call_id: Tool call identifier.

    Returns:
        ModelRequest containing a ToolReturnPart.
    """
    return ModelRequest(
        parts=[
            UserPromptPart(content=""),
            ToolReturnPart(
                content=content,
                tool_name=tool_name,
                tool_call_id=tool_call_id,
            ),
        ]
    )


def make_message_history(
    messages: list[dict[str, Any]],
) -> list[Any]:
    """Create a PydanticAI message history from a simplified format.

    This fixture supports testing prune/compaction on standard patterns.

    Args:
        messages: List of message dicts with 'role' and 'content' keys.
            role: "user", "assistant", or "tool"
            content: Message content string
            tool_name: (for tool) tool name
            tool_call_id: (for tool) tool call ID

    Returns:
        List of PydanticAI ModelRequest/ModelResponse objects.
    """
    history: list[Any] = []

    for msg in messages:
        role = msg["role"]
        content = msg.get("content", "")

        if role == "user":
            history.append(ModelRequest(parts=[UserPromptPart(content=str(content))]))
        elif role == "assistant":
            history.append(ModelResponse(parts=[TextPart(content=str(content))]))
        elif role == "tool":
            tool_name = msg.get("tool_name", "tool")
            tool_call_id = msg.get("tool_call_id", f"call_{len(history)}")
            history.append(
                ModelRequest(
                    parts=[
                        ToolReturnPart(
                            content=str(content),
                            tool_name=str(tool_name),
                            tool_call_id=str(tool_call_id),
                        )
                    ]
                )
            )
        elif role == "system":
            # System messages are ModelRequest with system content
            history.append(ModelRequest(parts=[UserPromptPart(content=str(content))]))

    return history


# =============================================================================
# SECTION 3: Anima-Owned Conversation Type Fixtures
# =============================================================================


def make_conversation_message(
    role: ConversationRole,
    content: str,
    speaker_id: str | None = None,
) -> dict[str, Any]:
    """Create an anima-owned ConversationMessageView-compatible dict.

    Used for testing prune/compaction on anima-owned message types
    that will be moved during Wave 4 schema cleanup.

    Args:
        role: Message role (user, assistant, tool).
        content: Message content.
        speaker_id: Optional speaker identifier.

    Returns:
        Dict matching ConversationMessageView protocol.
    """
    return {
        "role": role,
        "content": content,
        "speaker_id": speaker_id,
    }


def make_conversation_summary(
    summary_id: str,
    model_id: str,
    prompt_digest: str | None = None,
    input_digest: str | None = None,
    created_at: datetime | None = None,
) -> dict[str, Any]:
    """Create an anima-owned ConversationSummaryView-compatible dict.

    Args:
        summary_id: Unique summary identifier.
        model_id: Model that generated the summary.
        prompt_digest: Optional digest of the prompt used.
        input_digest: Optional digest of the input content.
        created_at: Creation timestamp (defaults to now).

    Returns:
        Dict matching ConversationSummaryView protocol.
    """
    return {
        "summary_id": summary_id,
        "model_id": model_id,
        "prompt_digest": prompt_digest,
        "input_digest": input_digest,
        "created_at": created_at or datetime.utcnow(),
    }


def make_context_bundle(
    bundle_id: str,
    raw_tail: list[RawTailEntry] | None = None,
    inherited_from: str | None = None,
    summary_refs: list[SummaryRef] | None = None,
) -> ContextBundle:
    """Create a ContextBundle for shared-context handoff testing.

    Args:
        bundle_id: Unique bundle identifier.
        raw_tail: Optional raw conversation tail messages.
        inherited_from: Optional parent instance ID.
        summary_refs: Optional summary references.

    Returns:
        ContextBundle instance.
    """
    return ContextBundle(
        bundle_id=bundle_id,
        raw_tail=raw_tail or [],
        inherited_from=inherited_from,
    )


def make_raw_tail_entry(
    role: Literal["user", "assistant", "tool"],
    content: str,
) -> RawTailEntry:
    """Create a RawTailEntry for conversation context bundle testing.

    Args:
        role: Message role (user, assistant, or tool).
        content: Message content.

    Returns:
        RawTailEntry instance.
    """
    return RawTailEntry(role=role, content=content)


def make_summary_ref(
    summary_id: str,
    model_id: str,
    created_at: datetime | None = None,
) -> SummaryRef:
    """Create a SummaryRef for conversation summary testing.

    Args:
        summary_id: Summary identifier.
        model_id: Model that generated the summary.
        created_at: Creation timestamp (defaults to now).

    Returns:
        SummaryRef instance.
    """
    return SummaryRef(
        summary_id=summary_id,
        model_id=model_id,
        created_at=created_at or datetime.now(timezone.utc),
    )


def make_compaction_config(
    auto: bool = True,
    prune: bool = True,
    prompt: str | None = None,
) -> CompactionConfig:
    """Create a CompactionConfig for compaction testing.

    Args:
        auto: Whether auto-compaction is enabled.
        prune: Whether tool output pruning is enabled.
        prompt: Optional custom compaction prompt.

    Returns:
        CompactionConfig instance.
    """
    return CompactionConfig(auto=auto, prune=prune, prompt=prompt)


# =============================================================================
# SECTION 4: Session View Fixtures for Prune/Compaction Testing
# =============================================================================


class _MockConversationMessage:
    """Mock implementation of ConversationMessageView for testing.

    Matches the protocol defined in core.conversation.contracts.
    """

    def __init__(
        self,
        role: ConversationRole,
        content: str,
        speaker_id: str | None = None,
    ) -> None:
        self.role = role
        self.content = content
        self.speaker_id = speaker_id


class _MockConversationSummary:
    """Mock implementation of ConversationSummaryView for testing.

    Matches the protocol defined in core.conversation.contracts.
    """

    def __init__(
        self,
        summary_id: str,
        model_id: str,
        prompt_digest: str | None = None,
        input_digest: str | None = None,
        created_at: datetime | None = None,
    ) -> None:
        self.summary_id = summary_id
        self.model_id = model_id
        self.prompt_digest = prompt_digest
        self.input_digest = input_digest
        self.created_at = created_at or datetime.utcnow()


class _MockCompactionSessionView:
    """Mock implementation of CompactionSessionView for testing.

    Matches the protocol defined in core.conversation.contracts.
    """

    def __init__(
        self,
        raw_tail: list[_MockConversationMessage],
        summary_refs: list[_MockConversationSummary] | None = None,
        artifact_refs: list[str] | None = None,
    ) -> None:
        self.raw_tail = raw_tail
        self.summary_refs = summary_refs or []
        self.artifact_refs = artifact_refs or []


def make_compaction_session_view(
    messages: list[tuple[ConversationRole, str]],
    summary_ids: list[str] | None = None,
    artifact_ids: list[str] | None = None,
) -> _MockCompactionSessionView:
    """Create a CompactionSessionView for prune/compaction testing.

    Args:
        messages: List of (role, content) tuples.
        summary_ids: Optional list of summary IDs.
        artifact_ids: Optional list of artifact IDs.

    Returns:
        CompactionSessionView implementation.
    """
    raw_tail = [
        _MockConversationMessage(role=role, content=content)
        for role, content in messages
    ]

    summary_refs = (
        [
            _MockConversationSummary(
                summary_id=summary_id,
                model_id="test-model",
                created_at=datetime.utcnow(),
            )
            for summary_id in summary_ids
        ]
        if summary_ids
        else []
    )

    return _MockCompactionSessionView(
        raw_tail=raw_tail,
        summary_refs=summary_refs,
        artifact_refs=artifact_ids or [],
    )


# =============================================================================
# SECTION 5: Message History Fixtures for History Testing
# =============================================================================


def make_simple_user_assistant_history(
    turns: int = 3,
    prefix: str = "message",
) -> list[Any]:
    """Create a simple alternating user/assistant history.

    Args:
        turns: Number of user-assistant turns.
        prefix: Message content prefix.

    Returns:
        List of ModelRequest/ModelResponse objects.
    """
    history: list[Any] = []

    for i in range(turns):
        history.append(
            ModelRequest(parts=[UserPromptPart(content=f"{prefix}_user_{i}")])
        )
        history.append(
            ModelResponse(parts=[TextPart(content=f"{prefix}_assistant_{i}")])
        )

    return history


def make_history_with_tool_calls(
    user_turns: int = 2,
    tools_per_turn: int = 1,
    tool_result_size: int = 100,
) -> list[Any]:
    """Create history with tool result content.

    Use this for testing tool output pruning on large tool results.

    Args:
        user_turns: Number of user turns.
        tools_per_turn: Tool calls per user turn.
        tool_result_size: Size of tool result content in characters.

    Returns:
        List of ModelRequest/ModelResponse objects with tool results.
    """
    history: list[Any] = []

    for turn in range(user_turns):
        history.append(ModelRequest(parts=[UserPromptPart(content=f"query_{turn}")]))
        history.append(ModelResponse(parts=[TextPart(content=f"response_{turn}")]))

        for tool_idx in range(tools_per_turn):
            large_content = "x" * tool_result_size
            history.append(
                ModelRequest(
                    parts=[
                        ToolReturnPart(
                            content=large_content,
                            tool_name=f"tool_{tool_idx}",
                            tool_call_id=f"call_{turn}_{tool_idx}",
                        )
                    ]
                )
            )

    return history


def make_compaction_candidate_history(
    pre_compaction_turns: int = 5,
    max_turns: int = 3,
) -> tuple[list[Any], list[Any]]:
    """Create a history suitable for compaction testing.

    Creates a long history that should be compacted, and returns
    the boundary index for the "recent" messages to preserve.

    Args:
        pre_compaction_turns: Number of turns before compaction boundary.
        max_turns: Number of turns to preserve after boundary.

    Returns:
        Tuple of (full_history, recent_history).
    """
    full_history: list[Any] = []

    # System message
    full_history.append(ModelRequest(parts=[UserPromptPart(content="system prompt")]))

    # Pre-compaction messages (older, to be compacted)
    for i in range(pre_compaction_turns):
        full_history.append(
            ModelRequest(parts=[UserPromptPart(content=f"old_user_{i}")])
        )
        full_history.append(
            ModelResponse(parts=[TextPart(content=f"old_assistant_{i}")])
        )

    # Compaction boundary marker
    boundary_idx = len(full_history)

    # Recent messages (to preserve)
    for i in range(max_turns):
        full_history.append(
            ModelRequest(parts=[UserPromptPart(content=f"recent_user_{i}")])
        )
        full_history.append(
            ModelResponse(parts=[TextPart(content=f"recent_assistant_{i}")])
        )

    recent_history = full_history[boundary_idx:]

    return full_history, recent_history


def make_overflow_history(
    token_count: int = 200_000,
    reserved: int = 20_000,
    threshold_ratio: float = 0.9,
) -> tuple[list[Any], int]:
    """Create a history that triggers overflow threshold.

    Args:
        token_count: Total token budget.
        reserved: Reserved token count.
        threshold_ratio: Fraction of budget before overflow.

    Returns:
        Tuple of (overflow_history, boundary_index).
    """
    # Create enough messages to exceed threshold
    messages_needed = int((token_count - reserved) // (1000 // threshold_ratio))

    history: list[Any] = [ModelRequest(parts=[UserPromptPart(content="system")])]

    for i in range(messages_needed):
        history.append(ModelRequest(parts=[UserPromptPart(content=f"user_{i}" * 50)]))
        history.append(ModelResponse(parts=[TextPart(content=f"assistant_{i}" * 50)]))

    # Boundary is the last 2 messages (1 user + 1 assistant turn)
    boundary_idx = len(history) - 2

    return history, boundary_idx


__all__ = [
    # Type definitions
    "ConversationRole",
    # PydanticAI message factories
    "make_user_message",
    "make_assistant_message",
    "make_tool_result_message",
    "make_message_history",
    # Anima-owned conversation types
    "make_conversation_message",
    "make_conversation_summary",
    "make_context_bundle",
    "make_raw_tail_entry",
    "make_summary_ref",
    "make_compaction_config",
    # Session view fixtures
    "make_compaction_session_view",
    "_MockConversationMessage",
    "_MockConversationSummary",
    "_MockCompactionSessionView",
    # History fixtures
    "make_simple_user_assistant_history",
    "make_history_with_tool_calls",
    "make_compaction_candidate_history",
    "make_overflow_history",
]
