import os
import uuid

from pydantic import BaseModel, Field
from pydantic_settings import BaseSettings


class ContextSchema(BaseModel):
    """The configuration for the agent."""

    instructions: str = Field(default="", description="Additional instructions for the agent.")

    model: str = Field(
        default=os.getenv("CHAT_MODEL", "anthropic:claude-sonnet-4-5"),
        description="The model to use, in format `provider:full-model-name`.",
    )

    auth_token: str | None = Field(default=None, description="Auth token for registries.")

    agent_id: str | None = Field(
        default=None, description="The agent ID to use. If both template and agent are set, agent id will be used"
    )

    template_id: str | None = Field(default=None, description="The template ID to use.")
    thread_id: str = Field(default=str(uuid.uuid4()), description="Thread id for the agent run")
    timezone: str = Field(default="UTC", description="The user's timezone.")

    eval_mode: bool = Field(
        default=False,
        description="Set to true when running evals. Implements completion and execution of created agent for testing.",
    )

    is_scheduled_run: bool = Field(
        default=False,
        description="Set to true if this is a scheduled run execution.",
    )


# ============================================================================
# CENTRALIZED CONFIGURATION MODELS
# ============================================================================


class SandboxConfig(BaseSettings):
    """Configuration for sandbox execution environment."""

    timeout: int = Field(
        default=600,
        description="Sandbox timeout in seconds",
    )

    model_config = {"env_prefix": "SANDBOX_"}


class ToolConfig(BaseModel):
    """Configuration for tool discovery and loading."""

    tool_search_threshold: float = Field(
        default=0.75,
        description="Distance threshold for tool search relevance",
    )
    app_search_threshold: float = Field(
        default=0.7,
        description="Distance threshold for app search relevance",
    )
    search_limit: int = Field(
        default=20,
        description="Maximum number of tools returned in search",
    )


class RetryConfig(BaseModel):
    """Configuration for retry behavior."""

    max_retries: int = Field(
        default=3,
        description="Maximum retry attempts for operations",
    )


class LLMConfig(BaseSettings):
    """Configuration for LLM behavior.
    Chat model and agent builder model have to be same family
    """

    default_model: str = Field(
        default="gemini:gemini-3-pro-preview",
        description="Default chat model in format `provider:full-model-name`",
        alias="CHAT_MODEL",
    )
    fallback_model: str = Field(
        default="gemini:gemini-3-pro-preview",
        description="Fallback model when primary model fails",
    )


class GraphConfig(BaseModel):
    """Configuration for LangGraph execution."""

    recursion_limit: int = Field(
        default=75,
        description="Maximum recursion depth for graph execution",
    )


class CodeActConfig(BaseModel):
    """
    Main configuration class aggregating all component configs.

    This is the single source of truth for CodeAct agent configuration.
    """

    sandbox: SandboxConfig = Field(
        default_factory=SandboxConfig,
        description="Sandbox execution configuration",
    )
    tools: ToolConfig = Field(
        default_factory=ToolConfig,
        description="Tool discovery and loading configuration",
    )
    retry: RetryConfig = Field(
        default_factory=RetryConfig,
        description="Retry behavior configuration",
    )
    llm: LLMConfig = Field(
        default_factory=LLMConfig,
        description="LLM behavior and model configuration",
    )
    graph: GraphConfig = Field(
        default_factory=GraphConfig,
        description="LangGraph execution configuration",
    )


# ============================================================================
# GLOBAL CONFIG INSTANCE (Singleton Pattern)
# ============================================================================

_config_instance: CodeActConfig | None = None


def get_config() -> CodeActConfig:
    """Get the global config instance (singleton pattern)."""
    global _config_instance  # noqa: PLW0603
    if _config_instance is None:
        _config_instance = CodeActConfig()
    return _config_instance
