from collections.abc import Mapping
from enum import Enum
from typing import Any, Union

from pydantic import BaseModel, Field, field_validator

from dify_plugin.core.documentation.schema_doc import docs
from dify_plugin.core.utils.yaml_loader import load_yaml_file
from dify_plugin.entities import I18nObject
from dify_plugin.entities.invoke_message import InvokeMessage
from dify_plugin.entities.tool import (
    CommonParameterType,
    ParameterAutoGenerate,
    ParameterTemplate,
    ToolIdentity,
    ToolParameterOption,
    ToolProviderIdentity,
)


@docs(
    description="The identity of the agent strategy provider",
)
class AgentStrategyProviderIdentity(ToolProviderIdentity):
    pass


class AgentRuntime(BaseModel):
    user_id: str | None


@docs(
    description="The feature of the agent strategy",
)
class AgentStrategyFeature(str, Enum):
    HISTORY_MESSAGES = "history-messages"


@docs(
    description="The identity of the agent strategy",
)
class AgentStrategyIdentity(ToolIdentity):
    pass


@docs(
    description="The parameter of the agent strategy",
)
class AgentStrategyParameter(BaseModel):
    class ToolParameterType(str, Enum):
        STRING = CommonParameterType.STRING.value
        NUMBER = CommonParameterType.NUMBER.value
        BOOLEAN = CommonParameterType.BOOLEAN.value
        SELECT = CommonParameterType.SELECT.value
        SECRET_INPUT = CommonParameterType.SECRET_INPUT.value
        FILE = CommonParameterType.FILE.value
        FILES = CommonParameterType.FILES.value
        MODEL_SELECTOR = CommonParameterType.MODEL_SELECTOR.value
        APP_SELECTOR = CommonParameterType.APP_SELECTOR.value
        TOOLS_SELECTOR = CommonParameterType.TOOLS_SELECTOR.value
        # TOOL_SELECTOR = CommonParameterType.TOOL_SELECTOR.value
        ANY = CommonParameterType.ANY.value
        # MCP object and array type parameters
        OBJECT = CommonParameterType.OBJECT.value
        ARRAY = CommonParameterType.ARRAY.value

    name: str = Field(..., description="The name of the parameter")
    label: I18nObject = Field(..., description="The label presented to the user")
    help: I18nObject | None = None
    type: ToolParameterType = Field(..., description="The type of the parameter")
    auto_generate: ParameterAutoGenerate | None = Field(default=None, description="The auto generate of the parameter")
    template: ParameterTemplate | None = Field(default=None, description="The template of the parameter")
    scope: str | None = None
    required: bool | None = False
    default: Union[int, float, str] | None = None
    min: Union[float, int] | None = None
    max: Union[float, int] | None = None
    precision: int | None = None
    options: list[ToolParameterOption] | None = None


@docs(
    name="Python",
    description="The extra of the agent strategy",
)
class Python(BaseModel):
    source: str


@docs(
    name="AgentStrategyExtra",
    description="The extra of the agent strategy",
)
class AgentStrategyConfigurationExtra(BaseModel):
    python: Python


@docs(
    name="AgentStrategy",
    description="The Manifest of the agent strategy",
)
class AgentStrategyConfiguration(BaseModel):
    identity: AgentStrategyIdentity
    parameters: list[AgentStrategyParameter] = Field(default=[], description="The parameters of the agent")
    description: I18nObject
    extra: AgentStrategyConfigurationExtra
    has_runtime_parameters: bool = Field(default=False, description="Whether the tool has runtime parameters")
    output_schema: Mapping[str, Any] | None = None
    features: list[AgentStrategyFeature] = Field(default=[], description="The features of the agent")


@docs(
    name="AgentStrategyProviderExtra",
    description="The extra of the agent provider",
)
class AgentProviderConfigurationExtra(BaseModel):
    @docs(
        name="Python",
        description="The extra of the agent provider",
    )
    class Python(BaseModel):
        source: str

    python: Python


@docs(
    name="AgentStrategyProvider",
    description="The Manifest of the agent strategy provider",
    outside_reference_fields={"strategies": AgentStrategyConfiguration},
)
class AgentStrategyProviderConfiguration(BaseModel):
    identity: AgentStrategyProviderIdentity
    strategies: list[AgentStrategyConfiguration] = Field(default=[], description="The strategies of the agent provider")

    @field_validator("strategies", mode="before")
    @classmethod
    def validate_strategies(cls, value) -> list[AgentStrategyConfiguration]:
        if not isinstance(value, list):
            raise ValueError("strategies should be a list")

        strategies: list[AgentStrategyConfiguration] = []

        for strategy in value:
            # read from yaml
            if not isinstance(strategy, str):
                raise ValueError("strategy path should be a string")
            try:
                file = load_yaml_file(strategy)
                strategies.append(
                    AgentStrategyConfiguration(
                        **{
                            "identity": AgentStrategyIdentity(**file["identity"]),
                            "parameters": [
                                AgentStrategyParameter(**param) for param in file.get("parameters", []) or []
                            ],
                            "description": I18nObject(**file["description"]),
                            "extra": AgentStrategyConfigurationExtra(**file.get("extra", {})),
                            "features": file.get("features", []),
                        }
                    )
                )
            except Exception as e:
                raise ValueError(f"Error loading agent strategy configuration: {e!s}") from e

        return strategies


class AgentInvokeMessage(InvokeMessage):
    pass
