"""Sandboxed Python execution for RLM queries.

This module is the main facade composing the sandbox components:
- SqlValidator: Pure SQL validation and transformation
- MessageDatabase: Database connection and queries
- LlmOrchestrator: Recursive LLM sub-calls
- RestrictedExecutor: Sandboxed code execution

Each component is in its own module for focused testing and maintenance.
"""

from __future__ import annotations

from typing import Any, Callable, TYPE_CHECKING

# Re-export components for backward compatibility
from .exceptions import SandboxError, BatchLLMError
from .sql_validator import SqlValidator
from .message_database import (
    MessageDatabase,
    cf_to_datetime,
    unix_seconds_to_cf_nanoseconds,
    MAC_EPOCH_OFFSET,
)
from .llm_orchestrator import LlmOrchestrator
from .restricted_executor import RestrictedExecutor
from .llm_client import ModelRelayLLMClient, create_default_llm_client

from .execution_context import DEFAULT_MAX_OUTPUT_CHARS

if TYPE_CHECKING:
    from .execution_context import ExecutionContext
    from .llm_client import LLMClient


class Sandbox:
    """Facade for sandboxed Python execution with database access and LLM calls.

    Composes MessageDatabase, LlmOrchestrator, and RestrictedExecutor.

    This is a thin facade - it delegates to the composed components rather
    than re-implementing their functionality.

    Dependencies are injectable for testing:
    - time_provider: Callable returning current Unix time
    - quiet: If True, suppress logging to stderr
    - llm_client: LLMClient for LLM API calls (optional, uses default if not provided)
    """

    def __init__(
        self,
        db_path: str,
        contacts_db_path: str | None = None,
        context: ExecutionContext | None = None,
        subcall_model: str | None = None,
        *,
        time_provider: Callable[[], float] | None = None,
        quiet: bool = False,
        llm_client: LLMClient | None = None,
        answer: dict[str, Any] | None = None,
    ) -> None:
        self._db = MessageDatabase(
            db_path,
            contacts_db_path=contacts_db_path,
            time_provider=time_provider,
            quiet=quiet,
        )

        self._llm: LlmOrchestrator | None = None
        if context is not None:
            client = llm_client or create_default_llm_client()
            self._llm = LlmOrchestrator(context, client, subcall_model=subcall_model)

        # Answer dict for iterative refinement (Prime Intellect RLM pattern)
        self._answer = answer if answer is not None else {"content": "", "ready": False}
        max_output_chars = context.config.max_output_chars if context is not None else DEFAULT_MAX_OUTPUT_CHARS
        self._executor = RestrictedExecutor(
            self._db,
            self._llm,
            self._answer,
            max_output_chars=max_output_chars,
        )
        self._context = context

    # Core execution method
    def execute(self, code: str) -> str:
        """Execute Python code in the sandbox."""
        return self._executor.execute(code)

    # Direct component access for those who need it
    @property
    def db(self) -> MessageDatabase:
        """Access the database component directly."""
        return self._db

    @property
    def llm(self) -> LlmOrchestrator | None:
        """Access the LLM orchestrator component directly."""
        return self._llm

    @property
    def executor(self) -> RestrictedExecutor:
        """Access the executor component directly."""
        return self._executor

    @property
    def answer(self) -> dict[str, Any]:
        """Access the answer dict for iterative refinement."""
        return self._answer

    def _require_llm(self, method_name: str) -> LlmOrchestrator:
        """Return the LLM orchestrator or raise if unavailable."""
        if self._llm is None:
            raise SandboxError(f"{method_name} requires an ExecutionContext")
        return self._llm

    def get_retrieved_guids(self) -> list[str]:
        """Return the list of GUIDs retrieved during query execution."""
        return self._db.get_retrieved_guids()

    def close(self) -> None:
        """Close the database connection."""
        self._db.close()

    # Database methods - delegated to self._db
    def query(self, sql: str) -> list[dict[str, Any]]:
        """Execute a read-only SQL query."""
        return self._db.query(sql)

    def search(
        self,
        terms: str,
        limit: int = 1000,
        sender: str | None = None,
        chat: str | None = None,
        days: int | None = None,
        table: str = "messages",
    ) -> list[dict[str, Any]]:
        """Search messages or events using FTS."""
        return self._db.search(terms, limit, sender, chat, days, table)

    def get_messages(self, limit: int | None = 10000) -> list[dict[str, Any]]:
        """Get messages in bulk."""
        return self._db.get_messages(limit)

    def get_stats(self) -> dict[str, Any]:
        """Get database statistics."""
        return self._db.get_stats()

    def get_chats(self) -> list[dict[str, Any]]:
        """Get list of all chats."""
        return self._db.get_chats()

    def get_recent(self, days: int = 30, limit: int = 10000) -> list[dict[str, Any]]:
        """Get recent messages."""
        return self._db.get_recent(days, limit)

    def sample(self, n: int = 1000) -> list[dict[str, Any]]:
        """Get random sample of messages."""
        return self._db.sample(n)

    def get_chat_messages(self, chat_id: str, limit: int = 1000) -> list[dict[str, Any]]:
        """Get messages from a specific chat."""
        return self._db.get_chat_messages(chat_id, limit)

    # LLM methods - delegated to self._llm
    def llm_query(self, prompt: str, context_data: str = "") -> str:
        """Execute a recursive LLM sub-query."""
        return self._require_llm("llm_query").llm_query(prompt, context_data)

    @staticmethod
    def chunk_data(data: list[Any], chunk_size: int) -> list[list[Any]]:
        """Split data into chunks."""
        return LlmOrchestrator.chunk_data(data, chunk_size)

    def batch_llm_query(
        self,
        prompts: list[str],
        contexts: list[str] | None = None,
        *,
        max_concurrent: int | None = None,
        fail_fast: bool | None = None,
        timeout_ms: int | None = None,
    ) -> list[str]:
        """Execute multiple llm_query calls."""
        return self._require_llm("batch_llm_query").batch_llm_query(
            prompts, contexts, max_concurrent=max_concurrent, fail_fast=fail_fast, timeout_ms=timeout_ms
        )


# Re-export for backward compatibility
__all__ = [
    # Main facade
    "Sandbox",
    # Exceptions
    "SandboxError",
    "BatchLLMError",
    # Components
    "SqlValidator",
    "MessageDatabase",
    "LlmOrchestrator",
    "RestrictedExecutor",
    # Utilities
    "cf_to_datetime",
    "unix_seconds_to_cf_nanoseconds",
    "MAC_EPOCH_OFFSET",
]
