# 中文注释：
# 文件：echobot/config/schema.py
# 说明：配置加载与配置模型定义。

"""Configuration schema using Pydantic."""

from pathlib import Path
from typing import Literal

from pydantic import BaseModel, Field
from pydantic_settings import BaseSettings


class TelegramConfig(BaseModel):
    """Telegram channel configuration."""

    enabled: bool = False
    token: str = ""  # Bot token from @BotFather
    allow_from: list[str] = Field(default_factory=list)  # Allowed user IDs or usernames


class ChannelsConfig(BaseModel):
    """Configuration for chat channels."""

    telegram: TelegramConfig = Field(default_factory=TelegramConfig)


class AgentDefaults(BaseModel):
    """Default agent configuration."""

    workspace: str = "~/.echobot/workspace"
    model: str = "anthropic/claude-opus-4-5"
    max_tokens: int = 8192
    temperature: float = 0.7
    max_tool_iterations: int = 20
    default_role: str | None = None
    # exec 工具允许访问的额外目录列表
    # 默认允许访问整个 home 目录
    exec_allowed_dirs: list[str] = Field(default_factory=list)
    # ==================== 上下文压缩相关 ====================
    # 是否启用会话历史滚动压缩。
    # 启用后会把较早对话压缩为摘要，减少上下文 token 占用。
    context_compression_enabled: bool = True
    # 历史消息达到该阈值后触发压缩。
    context_history_max_messages: int = 40
    # 压缩后保留的最近消息数（原文保留，避免最新上下文损失）。
    context_keep_recent_messages: int = 16
    # 历史摘要最大字符数。
    context_summary_max_chars: int = 2000
    # ==================== 安全审批相关 ====================
    # 待审批请求有效期（秒）。
    approval_ttl_seconds: int = 15 * 60
    # 需要人工审批的工具列表。
    # 默认包含 exec / write_file / edit_file / run_skill_script。
    # 若显式配置为空数组 []，表示不强制任何工具审批（仍可配合 deniedExecPatterns 做硬阻断）。
    approval_required_tools: list[str] = Field(
        default_factory=lambda: ["exec", "write_file", "edit_file", "run_skill_script"]
    )
    # exec 命令的“直接拒绝”正则规则。
    # 命中任意规则会直接 deny，不进入审批。
    denied_exec_patterns: list[str] = Field(
        default_factory=lambda: [
            r"(^|[;&|])\s*rm\s+-rf\s+/\s*($|[;&|])",
            r"(^|[;&|])\s*shutdown\b",
            r"(^|[;&|])\s*reboot\b",
            r"(^|[;&|])\s*mkfs(\.| )",
            r"(^|[;&|])\s*dd\s+if=",
            r"curl\s+[^|]*\|\s*(sh|bash)",
            r"wget\s+[^|]*\|\s*(sh|bash)",
        ]
    )
    # ==================== Webhook 相关 ====================
    # 入站 webhook 验签密钥（建议通过环境变量或本地 config 配置）。
    webhook_secret: str = ""
    """
    默认角色名称。
    
    当启动网关或运行 agent 时，如果没有通过 --role 参数指定角色，
    将使用此配置中设置的默认角色。
    
    示例值: "default", "coder", "admin", "support"
    """


class AgentsConfig(BaseModel):
    """Agent configuration."""

    defaults: AgentDefaults = Field(default_factory=AgentDefaults)


# ============================================================================
# Multi-Agent Configuration
# ============================================================================


class AgentBindingMatch(BaseModel):
    """Match conditions for agent binding."""

    channel: str = "telegram"
    chat_type: str | None = None  # private, group, supergroup
    mention: str | None = None  # @username to route to specific agent


class AgentBinding(BaseModel):
    """Binding rule for routing messages to agents."""

    agent_id: str
    match: AgentBindingMatch = Field(default_factory=AgentBindingMatch)
    broadcast: bool = False  # If true, send to all matching agents


class CollaborationConfig(BaseModel):
    """Collaboration settings between agents."""

    enabled: bool = True
    allow_calls: list[str] = Field(default_factory=list)  # Agent IDs that can be called


class AgentInstanceConfig(BaseModel):
    """Configuration for a single agent instance."""

    id: str
    name: str
    token: str
    role: str = "default"
    workspace: str = "~/.echobot/workspace"
    enabled: bool = True


class MultiAgentsConfig(BaseModel):
    """Configuration for multiple agents."""

    agents: list[AgentInstanceConfig] = Field(default_factory=list)
    collaboration: CollaborationConfig = Field(default_factory=CollaborationConfig)
    bindings: list[AgentBinding] = Field(default_factory=list)


class ProviderConfig(BaseModel):
    """LLM provider configuration."""

    api_key: str = ""
    api_base: str | None = None
    extra_headers: dict[str, str] | None = None


class ProvidersConfig(BaseModel):
    """Configuration for LLM providers."""

    anthropic: ProviderConfig = Field(default_factory=ProviderConfig)
    openai: ProviderConfig = Field(default_factory=ProviderConfig)
    openrouter: ProviderConfig = Field(default_factory=ProviderConfig)
    deepseek: ProviderConfig = Field(default_factory=ProviderConfig)
    groq: ProviderConfig = Field(default_factory=ProviderConfig)
    zhipu: ProviderConfig = Field(default_factory=ProviderConfig)
    dashscope: ProviderConfig = Field(default_factory=ProviderConfig)
    vllm: ProviderConfig = Field(default_factory=ProviderConfig)
    gemini: ProviderConfig = Field(default_factory=ProviderConfig)
    moonshot: ProviderConfig = Field(default_factory=ProviderConfig)
    minimax: ProviderConfig = Field(default_factory=ProviderConfig)
    aihubmix: ProviderConfig = Field(default_factory=ProviderConfig)


class GatewayConfig(BaseModel):
    """Gateway/server configuration."""

    host: str = "0.0.0.0"
    port: int = 18790


class WebSearchConfig(BaseModel):
    """Web search tool configuration."""

    api_key: str = ""  # Brave Search API key
    max_results: int = 5


class WebToolsConfig(BaseModel):
    """Web tools configuration."""

    search: WebSearchConfig = Field(default_factory=WebSearchConfig)


class ToolsConfig(BaseModel):
    """Tools configuration."""

    web: WebToolsConfig = Field(default_factory=WebToolsConfig)


class MCPServerConfig(BaseModel):
    """Single MCP server configuration."""

    model_config = {"env_nested_delimiter": None}  # Prevent key conversion

    type: Literal["stdio", "http"] = "stdio"

    # stdio type configuration
    command: str | None = None  # e.g., "npx"
    args: list[str] | None = None  # e.g., ["-y", "@some/mcp-server"]
    env: dict[str, str] | None = None

    # http type configuration
    url: str | None = None
    headers: dict[str, str] | None = None


class MCPConfig(BaseModel):
    """MCP (Model Context Protocol) configuration."""

    enabled: bool = False
    servers: dict[str, MCPServerConfig] = Field(default_factory=dict)


class Config(BaseSettings):
    """Root configuration for echobot."""

    agents: AgentsConfig = Field(default_factory=AgentsConfig)
    channels: ChannelsConfig = Field(default_factory=ChannelsConfig)
    providers: ProvidersConfig = Field(default_factory=ProvidersConfig)
    gateway: GatewayConfig = Field(default_factory=GatewayConfig)
    tools: ToolsConfig = Field(default_factory=ToolsConfig)
    mcp: MCPConfig = Field(default_factory=MCPConfig)

    @property
    def workspace_path(self) -> Path:
        """Get expanded workspace path."""
        return Path(self.agents.defaults.workspace).expanduser()

    def _match_provider(self, model: str | None = None) -> tuple["ProviderConfig | None", str | None]:
        """Match provider config and its registry name. Returns (config, spec_name)."""
        from echobot.providers.registry import PROVIDERS

        model_lower = (model or self.agents.defaults.model).lower()

        # Match by keyword (order follows PROVIDERS registry)
        for spec in PROVIDERS:
            provider = getattr(self.providers, spec.name, None)
            if provider and any(kw in model_lower for kw in spec.keywords) and provider.api_key:
                return provider, spec.name

        # Fallback: gateways first, then others (follows registry order)
        for spec in PROVIDERS:
            provider = getattr(self.providers, spec.name, None)
            if provider and provider.api_key:
                return provider, spec.name
        return None, None

    def get_provider(self, model: str | None = None) -> "ProviderConfig | None":
        """Get matched provider config (api_key, api_base, extra_headers)."""
        provider, _ = self._match_provider(model)
        return provider

    def get_provider_name(self, model: str | None = None) -> str | None:
        """Get matched registry provider name, e.g. "deepseek", "openrouter"."""
        _, name = self._match_provider(model)
        return name

    def get_api_key(self, model: str | None = None) -> str | None:
        """Get API key for the given model. Falls back to first available key."""
        provider = self.get_provider(model)
        return provider.api_key if provider else None

    def get_api_base(self, model: str | None = None) -> str | None:
        """Get API base for the given model. Gateways can use registry default base."""
        from echobot.providers.registry import find_by_name

        provider, name = self._match_provider(model)
        if provider and provider.api_base:
            return provider.api_base
        if name:
            spec = find_by_name(name)
            if spec and spec.is_gateway and spec.default_api_base:
                return spec.default_api_base
        return None

    class Config:
        env_prefix = "NANOBOT_"
        env_nested_delimiter = "__"
