"""Multi-turn simulation functionality."""

from typing import Callable, Optional, Dict, Any, List
from opik import id_helpers, track
from .simulated_user import SimulatedUser


def run_simulation(
    app: Callable,
    user_simulator: SimulatedUser,
    initial_message: Optional[str] = None,
    max_turns: int = 5,
    thread_id: Optional[str] = None,
    project_name: Optional[str] = None,
    **app_kwargs: Any,
) -> Dict[str, Any]:
    """
    Run a multi-turn conversation simulation between a simulated user and an app.

    1. The simulator passes single message strings to the app
    2. The app manages full conversation history internally using thread_id
    3. The app logs traces with thread_id for evaluation

    Args:
        app: Callable that processes messages and manages conversation history internally.
            Signature: app(message: str, *, thread_id: str, **kwargs) -> Dict[str, str]
            The app is automatically decorated with @track and thread_id is injected via opik_args.
        user_simulator: SimulatedUser instance that generates user responses
        initial_message: Optional initial message from the user. If None, generated by simulator
        max_turns: Maximum number of conversation turns (default: 5)
        thread_id: Optional thread ID for grouping traces. Generated if not provided
        project_name: Optional project name for trace logging
        **app_kwargs: Additional keyword arguments passed to the app

    Returns:
        Dict containing:
        - thread_id: The thread ID used for this simulation
        - conversation_history: List of message dicts from the simulation
        - project_name: Project name if provided
    """
    # Generate thread_id if not provided
    if thread_id is None:
        thread_id = id_helpers.generate_id()

    # Automatically decorate app if not already decorated
    if not hasattr(app, "opik_tracked"):
        app_name = app.__name__ if hasattr(app, "__name__") else "simulation_app"
        app = track(name=app_name)(app)

    # Track conversation for simulator (app manages its own history internally)
    conversation_history: List[Dict[str, str]] = []

    # Generate initial message if needed
    if initial_message is None:
        initial_message = user_simulator.generate_response(conversation_history)

    # Simulation loop
    for turn in range(max_turns):
        # Get user message
        if turn == 0:
            user_message_text = initial_message
        else:
            user_message_text = user_simulator.generate_response(conversation_history)

        # Create message dict for tracking
        user_message = {"role": "user", "content": user_message_text}
        conversation_history.append(user_message)

        # Call app with SINGLE message string, thread_id parameter, and opik_args for tracing
        try:
            assistant_message = app(
                user_message_text,
                thread_id=thread_id,
                **app_kwargs,
                opik_args={
                    "trace": {
                        "thread_id": thread_id,
                        "metadata": {"turn": turn + 1, "project_name": project_name},
                    }
                },
            )
        except Exception as e:
            # Handle app errors gracefully
            assistant_message = {
                "role": "assistant",
                "content": f"Error processing message: {str(e)}",
            }

        # Validate assistant message format
        if (
            not isinstance(assistant_message, dict)
            or "role" not in assistant_message
            or "content" not in assistant_message
        ):
            assistant_message = {
                "role": "assistant",
                "content": str(assistant_message)
                if assistant_message
                else "No response",
            }

        conversation_history.append(assistant_message)

    return {
        "thread_id": thread_id,
        "conversation_history": conversation_history,
        "project_name": project_name,
    }
