# -*- coding: utf-8 -*-
"""
工具基类
所有具体工具需继承此类
"""
import json
import os
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional


def coerce_int(value: Any, default: int = 0) -> int:
    """将任意值规整为 int，失败时返回 default。

    LLM 偶尔将 int 参数传为字符串（如 "10"），本函数负责安全转换，
    避免 str - int / str > int 等运算抛 TypeError。
    """
    if value is None:
        return default
    if isinstance(value, bool):
        # bool 是 int 子类，需单独处理，避免 True 被当作 1
        return int(value)
    if isinstance(value, int):
        return value
    if isinstance(value, float):
        return int(value)
    if isinstance(value, str):
        s = value.strip()
        if not s:
            return default
        try:
            # 先尝试 int，再尝试 float 截断
            return int(s)
        except ValueError:
            try:
                return int(float(s))
            except (ValueError, TypeError):
                return default
    # 其他类型（list/dict/None）兜底
    return default


def coerce_bool(value: Any, default: bool = False) -> bool:
    """将任意值规整为 bool，特别防御字符串 'false' 被判 truthy。

    LLM 偶尔将 bool 参数传为字符串 "false"/"true"，Python 中
    非空字符串均 truthy，若直接 `if value` 会把 "false" 当 True。
    无法明确判断的值（空串、空容器等）返回 default。
    """
    if value is None:
        return default
    if isinstance(value, bool):
        return value
    if isinstance(value, (int, float)):
        return bool(value)
    if isinstance(value, str):
        s = value.strip().lower()
        if s in ("true", "1", "yes", "y", "on"):
            return True
        if s in ("false", "0", "no", "n", "off"):
            return False
        # 空串或无法识别的字符串 → default
        return default
    # 容器类型（list/dict 等）按 truthy 规则
    return bool(value) if value else default


def _coerce_json_list(value: Any) -> List[Any]:
    """内部：若 value 是 JSON 字符串则反序列化为 list，否则返回原值或空 list。"""
    if isinstance(value, str):
        s = value.strip()
        if not s:
            return []
        try:
            parsed = json.loads(s)
        except (json.JSONDecodeError, ValueError):
            return []
        if not isinstance(parsed, list):
            return []
        return parsed
    if not isinstance(value, list):
        return []
    return value


def coerce_list_of_dicts(value: Any) -> List[Dict[str, Any]]:
    """将 value 规整为 list[dict]。

    LLM 偶尔将 list[dict] 参数传为 JSON 字符串（如 '[{"id":"1"}]'），
    直接迭代字符串会逐字符处理，后续 .get() 抛 AttributeError。
    本函数负责反序列化并过滤非 dict 项。
    """
    items = _coerce_json_list(value)
    return [t for t in items if isinstance(t, dict)]


def coerce_list_of_str(value: Any) -> List[str]:
    """将 value 规整为 list[str]，过滤非 str 项。

    LLM 偶尔将 list[str] 参数传为 JSON 字符串（如 '["a","b"]'），
    直接 join / set comprehension 会迭代字符串字符，导致静默数据损坏。
    """
    items = _coerce_json_list(value)
    return [t for t in items if isinstance(t, str)]


class Tool(ABC):
    """工具基类，所有具体工具需继承此类。"""

    @property
    @abstractmethod
    def name(self) -> str:
        """工具名称。"""
        pass

    @property
    @abstractmethod
    def description(self) -> str:
        """工具功能描述。"""
        pass

    @property
    @abstractmethod
    def parameters(self) -> Dict[str, Any]:
        """工具参数 JSON Schema。"""
        pass

    @abstractmethod
    def execute(self, **kwargs) -> Dict[str, Any]:
        """执行工具，返回结果字典。"""
        pass

    def to_openai_schema(self) -> Dict[str, Any]:
        """转换为 OpenAI Tool 格式。"""
        return {
            "type": "function",
            "function": {
                "name": self.name,
                "description": self.description,
                "parameters": self.parameters
            }
        }


# 受保护路径黑名单（借鉴 zhikuncode-main，防止 Agent 误删敏感文件）
PROTECTED_PATHS = [".git", ".env", ".ssh", ".gnupg", ".aws"]


def find_protected_name(path: str) -> Optional[str]:
    """查找路径命中的受保护黑名单项名称。

    用 os.path.normpath 规范化路径以处理 ``..`` 穿越与 Windows/Unix 分隔符差异，
    然后逐段检查路径组件是否在 PROTECTED_PATHS 中。命中返回该项名称，否则返回 None。
    """
    if not path:
        return None
    # normpath 统一分隔符（Windows 上为 \）并解析 .. / .
    norm = os.path.normpath(path)
    # 统一为 / 便于跨平台拆分组件
    parts = norm.replace("\\", "/").split("/")
    for part in parts:
        if part in PROTECTED_PATHS:
            return part
    return None


def is_protected_path(path: str) -> bool:
    """检测路径是否命中受保护路径黑名单。

    支持路径规范化（``..`` 穿越检测）与 Windows/Unix 路径分隔符兼容。
    """
    return find_protected_name(path) is not None
