import os
from typing import Any, Callable, Dict, List, Optional, Tuple, Union

import yaml
from tenacity import (
    retry,
    stop_after_attempt,
    wait_exponential,
    retry_if_exception_type,
)
from pydantic import (
    BaseModel,
    Field,
    field_validator,
)
from swarms.utils.loguru_logger import initialize_logger
from swarms.structs.agent import Agent
from swarms.structs.swarm_router import SwarmRouter
from swarms.utils.litellm import LiteLLM

logger = initialize_logger(log_folder="create_agents_from_yaml")


class AgentConfig(BaseModel):
    agent_name: str
    system_prompt: str
    model_name: Optional[str] = None
    max_loops: int = Field(default=1, ge=1)
    autosave: bool = True
    dashboard: bool = False
    verbose: bool = False
    dynamic_temperature_enabled: bool = False
    saved_state_path: Optional[str] = None
    user_name: str = "default_user"
    retry_attempts: int = Field(default=3, ge=1)
    context_length: int = Field(default=100000, ge=1000)
    return_step_meta: bool = False
    output_type: str = "str"
    auto_generate_prompt: bool = False
    artifacts_on: bool = False
    artifacts_file_extension: str = ".md"
    artifacts_output_path: str = ""

    @field_validator("system_prompt")
    @classmethod
    def validate_system_prompt(cls, v):
        if not v or not isinstance(v, str) or len(v.strip()) == 0:
            raise ValueError(
                "System prompt must be a non-empty string"
            )
        return v


class SwarmConfig(BaseModel):
    name: str
    description: str
    max_loops: int = Field(default=1, ge=1)
    swarm_type: str
    task: Optional[str] = None
    flow: Optional[Dict] = None
    autosave: bool = True
    return_json: bool = False
    rules: str = ""

    @field_validator("swarm_type")
    @classmethod
    def validate_swarm_type(cls, v):
        valid_types = {
            "SequentialWorkflow",
            "ConcurrentWorkflow",
            "AgentRearrange",
            "MixtureOfAgents",
            "auto",
        }
        if v not in valid_types:
            raise ValueError(
                f"Swarm type must be one of: {valid_types}"
            )
        return v


class YAMLConfig(BaseModel):
    agents: List[AgentConfig] = Field(..., min_length=1)
    swarm_architecture: Optional[SwarmConfig] = None

    model_config = {
        "extra": "forbid"  # Prevent additional fields not in the model
    }


def load_yaml_safely(
    yaml_file: str = None, yaml_string: str = None
) -> Dict:
    """Safely load and validate YAML configuration using Pydantic."""
    try:
        if yaml_string:
            config_dict = yaml.safe_load(yaml_string)
        elif yaml_file:
            if not os.path.exists(yaml_file):
                raise FileNotFoundError(
                    f"YAML file {yaml_file} not found."
                )
            with open(yaml_file, "r") as file:
                config_dict = yaml.safe_load(file)
        else:
            raise ValueError(
                "Either yaml_file or yaml_string must be provided"
            )

        # Validate using Pydantic
        config = YAMLConfig(**config_dict)
        return config_dict
    except yaml.YAMLError as e:
        raise ValueError(f"Error parsing YAML: {str(e)}")
    except Exception as e:
        raise ValueError(f"Error validating configuration: {str(e)}")


@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=4, max=10),
    retry=retry_if_exception_type((ConnectionError, TimeoutError)),
    before_sleep=lambda retry_state: logger.info(
        f"Retrying after error: {retry_state.outcome.exception()}"
    ),
)
def create_agent_with_retry(
    agent_config: Dict, model: LiteLLM
) -> Agent:
    """Create an agent with retry logic for handling transient failures."""
    try:
        validated_config = AgentConfig(**agent_config)
        agent = Agent(
            agent_name=validated_config.agent_name,
            system_prompt=validated_config.system_prompt,
            llm=model,
            max_loops=validated_config.max_loops,
            autosave=validated_config.autosave,
            dashboard=validated_config.dashboard,
            verbose=validated_config.verbose,
            dynamic_temperature_enabled=validated_config.dynamic_temperature_enabled,
            saved_state_path=validated_config.saved_state_path,
            user_name=validated_config.user_name,
            retry_attempts=validated_config.retry_attempts,
            context_length=validated_config.context_length,
            return_step_meta=validated_config.return_step_meta,
            output_type=validated_config.output_type,
            auto_generate_prompt=validated_config.auto_generate_prompt,
            artifacts_on=validated_config.artifacts_on,
            artifacts_file_extension=validated_config.artifacts_file_extension,
            artifacts_output_path=validated_config.artifacts_output_path,
        )
        return agent
    except Exception as e:
        logger.error(
            f"Error creating agent {agent_config.get('agent_name', 'unknown')}: {str(e)}"
        )
        raise


def create_agents_from_yaml(
    model: Callable = None,
    yaml_file: str = "agents.yaml",
    yaml_string: str = None,
    return_type: str = "auto",
) -> Union[
    SwarmRouter,
    Agent,
    List[Agent],
    Tuple[Union[SwarmRouter, Agent], List[Agent]],
    List[Dict[str, Any]],
]:
    """
    Create agents and/or SwarmRouter based on configurations defined in a YAML file or string.
    """
    agents = []
    task_results = []
    swarm_router = None

    try:
        # Load and validate configuration
        config = load_yaml_safely(yaml_file, yaml_string)

        # Create agents with retry logic
        for agent_config in config["agents"]:
            logger.info(
                f"Creating agent: {agent_config['agent_name']}"
            )

            if "model_name" in agent_config:
                model_instance = LiteLLM(
                    model_name=agent_config["model_name"]
                )
            else:
                model_name = "gpt-4o"
                model_instance = LiteLLM(model_name=model_name)

            agent = create_agent_with_retry(
                agent_config, model_instance
            )
            logger.info(
                f"Agent {agent_config['agent_name']} created successfully."
            )
            agents.append(agent)

        # Create SwarmRouter if specified
        if "swarm_architecture" in config:
            try:
                swarm_config = SwarmConfig(
                    **config["swarm_architecture"]
                )
                swarm_router = SwarmRouter(
                    name=swarm_config.name,
                    description=swarm_config.description,
                    max_loops=swarm_config.max_loops,
                    agents=agents,
                    swarm_type=swarm_config.swarm_type,
                    task=swarm_config.task,
                    flow=swarm_config.flow,
                    autosave=swarm_config.autosave,
                    return_json=swarm_config.return_json,
                    rules=swarm_config.rules,
                )
                logger.info(
                    f"SwarmRouter '{swarm_config.name}' created successfully."
                )
            except Exception as e:
                logger.error(f"Error creating SwarmRouter: {str(e)}")
                raise ValueError(
                    f"Failed to create SwarmRouter: {str(e)}"
                )

        # Handle return types with improved error checking
        valid_return_types = {
            "auto",
            "swarm",
            "agents",
            "both",
            "tasks",
            "run_swarm",
        }
        if return_type not in valid_return_types:
            raise ValueError(
                f"Invalid return_type. Must be one of: {valid_return_types}"
            )

        if return_type == "run_swarm" or "swarm":
            if not swarm_router:
                raise ValueError(
                    "Cannot run swarm: SwarmRouter not created."
                )
            try:
                return swarm_router.run(
                    config["swarm_architecture"]["task"]
                )
            except Exception as e:
                logger.error(f"Error running SwarmRouter: {str(e)}")
                raise

        # Return appropriate type based on configuration
        if return_type == "auto":
            return (
                swarm_router
                if swarm_router
                else (agents[0] if len(agents) == 1 else agents)
            )
        elif return_type == "swarm":
            return (
                swarm_router
                if swarm_router
                else (agents[0] if len(agents) == 1 else agents)
            )
        elif return_type == "agents":
            return agents[0] if len(agents) == 1 else agents
        elif return_type == "both":
            return (
                swarm_router
                if swarm_router
                else agents[0] if len(agents) == 1 else agents
            ), agents
        elif return_type == "tasks":
            return task_results

    except Exception as e:
        logger.error(
            f"Critical error in create_agents_from_yaml: {str(e)}"
        )
        raise
