"""Defines agent configuration types (the "Blueprint" dimension).

This module contains types for defining agent behavior, including model
selection, system prompts, tool configurations, execution parameters, and
lifecycle hooks for orchestrating tool execution at critical points.
Agents in Xulcan are data (JSON configurations), not code (classes).
"""
from enum import Enum
from typing import List, Literal, Optional
from pydantic import Field, model_validator

from xulcan.core.primitives import (
    CanonicalRecord,
    MachineID,
    ExternalID,
    HumanLabel,
    SemanticText,
    FinitePositiveFloat,
    SemanticVersion,
)

from xulcan.blueprint.components import (
    AgentToolConfig,
    LifecycleConfig
)

# ═══════════════════════════════════════════════════════════════════════════
# INTERNAL ENUMS (Not exported in __init__.py)
# ═══════════════════════════════════════════════════════════════════════════

class ModelProvider(str, Enum):
    """Internal enum for model provider validation.
    
    Uses Literal type at field level for cleaner API and JSON compatibility.
    This enum is kept for backward compatibility and internal routing only.
    """
    OPENAI = "openai"
    ANTHROPIC = "anthropic"
    GOOGLE = "google"
    GROQ = "groq"
    OLLAMA = "ollama"


# ═══════════════════════════════════════════════════════════════════════════
# AGENT CONFIGURATION TYPES
# ═══════════════════════════════════════════════════════════════════════════

class AgentBlueprint(CanonicalRecord):
    """The static configuration for an agent (the "DNA").
    
    This is the Blueprint dimension of Xulcan's Trinity. It defines what
    an agent is, but not what it has done (that's History) or how it
    communicates with LLMs (that's Protocol).
    
    Design Philosophy:
        - Agents are DATA, not CODE
        - Blueprints are versioned and immutable
        - Configuration is declarative and API-first
        - Lifecycle hooks enable advanced patterns without architectural pollution
    
    Attributes:
        id: Unique identifier (e.g., "customer-support-v1").
        name: Human-readable name for this agent.
        version: Semantic version of this configuration.
        description: What this agent does and when to use it.
        model_provider: Which LLM provider to use.
        model_name: Specific model identifier (e.g., "gpt-4", "claude-sonnet-4").
        temperature: Sampling temperature (0.0 = deterministic, 2.0 = creative).
        max_tokens: Hard limit on response length (None = provider default).
        system_prompt: Instructions that define the agent's behavior.
        tools: List of available tools with their configurations.
        lifecycle: Configuration for lifecycle hooks (on_start, on_finish, on_error).
        timeout_seconds: Maximum execution time before forced termination.
    
    Example:
        >>> blueprint = AgentBlueprint(
        ...     id="weather-assistant",
        ...     name="Weather Assistant",
        ...     version="1.0.0",
        ...     description="Provides weather information using web search",
        ...     model_provider="openai",
        ...     model_name="gpt-4",
        ...     system_prompt="You are a helpful weather assistant.",
        ...     tools=[
        ...         AgentToolConfig(name="web_search", enabled=True),
        ...         AgentToolConfig(name="rag_retriever", enabled=True)
        ...     ],
        ...     lifecycle=LifecycleConfig(
        ...         on_start=["rag_retriever"]
        ...     ),
        ...     timeout_seconds=300
        ... )
    """
    id: MachineID = Field(
        description="Unique identifier for this agent configuration"
    )
    
    name: HumanLabel = Field(
        description="Human-readable agent name"
    )
    
    version: SemanticVersion = Field(
        default="1.0.0",
        description="Semantic version (major.minor.patch)"
    )
    
    description: SemanticText = Field(
        default="",
        description="What this agent does and when to use it"
    )
    
    # Model Configuration
    model_provider: ModelProvider= Field(
        description="LLM provider to use for this agent"
    )
    
    model_name: ExternalID = Field(
        description="Specific model identifier (e.g., 'gpt-4', 'claude-sonnet-4')"
    )
    
    temperature: FinitePositiveFloat = Field(
        default=0.0,
        ge=0.0,
        le=2.0,
        description="Sampling temperature (0.0=deterministic, 2.0=creative)"
    )
    
    max_tokens: Optional[int] = Field(
        default=None,
        gt=0,
        description="Hard limit on output tokens (None=provider default)"
    )
    
    # Agent Behavior
    system_prompt: SemanticText = Field(
        description="Instructions that define the agent's behavior"
    )
    
    tools: List[AgentToolConfig] = Field(
        default_factory=list,
        description="Available tools with their configurations"
    )
    
    # Lifecycle Management
    lifecycle: LifecycleConfig = Field(
        default_factory=LifecycleConfig,
        description="Lifecycle hooks for tool execution at critical stages"
    )
    
    # Execution Constraints
    timeout_seconds: FinitePositiveFloat = Field(
        default=600,
        ge=1,
        le=3600,
        description="Maximum execution time before forced termination"
    )

    @property
    def all_active_tools(self) -> List[str]:
        """
        Tools available to the KERNEL (Lifecycle + Agent).
        Used for validation (checking if a tool exists).
        """
        return [tool.name for tool in self.tools if tool.enabled]

    @property
    def llm_tools(self) -> List[str]:
        """
        Tools visible to the AGENT (LLM).
        Only enabled AND exposed tools appear here.
        
        This is the list you send to OpenAI/Anthropic API.
        """
        return [
            tool.name for tool in self.tools 
            if tool.enabled and tool.exposed
        ]

    @property
    def has_llm_tools(self) -> bool:
        """Check if the agent has any tools visible to the LLM."""
        return len(self.llm_tools) > 0
    

    @model_validator(mode='after')
    def validate_tool_coherence(self) -> 'AgentBlueprint':
        # 1. Prevenir herramientas duplicadas
        tool_names = [t.name for t in self.tools]
        if len(tool_names) != len(set(tool_names)):
            raise ValueError("Duplicate tool names found in agent configuration.")
        
        # 2. Asegurar que los hooks usen herramientas registradas y habilitadas
        active_tools = set(self.all_active_tools)
        
        for stage_name, hooks in [
            ("on_start", self.lifecycle.on_start), 
            ("on_finish", self.lifecycle.on_finish), 
            ("on_error", self.lifecycle.on_error)
        ]:
            for hook in hooks:
                if hook.tool not in active_tools:
                    raise ValueError(
                        f"Lifecycle '{stage_name}' references tool '{hook.tool}', "
                        f"but it is not present or enabled in the agent's tools list."
                    )
        return self