import logging
import re
from typing import TypedDict

import yaml


logger = logging.getLogger(__name__)

# 磁盘读取上限属于 Skill 文档格式约束，市场扫描和 Agent 加载必须共用。
MAX_SKILL_FILE_SIZE = 1 * 1024 * 1024
MAX_SKILL_NAME_LENGTH = 64
MAX_SKILL_DESCRIPTION_LENGTH = 1024
MAX_SKILL_COMPATIBILITY_LENGTH = 500


class SkillMetadata(TypedDict):
    """描述符合 Agent Skills 规范的已校验元数据。"""

    path: str
    id: str
    name: str
    version: int
    description: str
    license: str | None
    compatibility: str | None
    metadata: dict[str, str]
    allowed_tools: list[str]


def _validate_metadata(raw: object, skill_path: str) -> dict[str, str]:
    """将 YAML metadata 字段规范化为字符串键值映射。"""
    if not isinstance(raw, dict):
        if raw:
            logger.warning(
                "Ignoring non-dict metadata in %s (got %s)",
                skill_path,
                type(raw).__name__,
            )
        return {}
    return {str(key): str(value) for key, value in raw.items()}


def parse_skill_metadata(  # noqa: C901
    content: str,
    skill_path: str,
    skill_id: str,
) -> SkillMetadata | None:
    """解析并校验一个 SKILL.md 的 YAML 前言。"""
    if len(content) > MAX_SKILL_FILE_SIZE:
        logger.warning(
            "Skipping %s: content too large (%d bytes)", skill_path, len(content)
        )
        return None

    match = re.match(r"^---\s*\n(.*?)\n---\s*\n", content, re.DOTALL)
    if not match:
        logger.warning("Skipping %s: no valid YAML frontmatter found", skill_path)
        return None

    try:
        frontmatter_data = yaml.safe_load(match.group(1))
    except yaml.YAMLError as err:
        logger.warning("Invalid YAML in %s: %s", skill_path, err)
        return None
    if not isinstance(frontmatter_data, dict):
        logger.warning("Skipping %s: frontmatter is not a mapping", skill_path)
        return None

    name = str(frontmatter_data.get("name", "")).strip()
    description = str(frontmatter_data.get("description", "")).strip()
    if not name or not description:
        logger.warning(
            "Skipping %s: missing required 'name' or 'description'", skill_path
        )
        return None
    if len(description) > MAX_SKILL_DESCRIPTION_LENGTH:
        logger.warning(
            "Description exceeds %d characters in %s, truncating",
            MAX_SKILL_DESCRIPTION_LENGTH,
            skill_path,
        )
        description = description[:MAX_SKILL_DESCRIPTION_LENGTH]

    raw_tools = frontmatter_data.get("allowed-tools")
    if isinstance(raw_tools, str):
        allowed_tools = [
            tool.strip(",")
            for tool in raw_tools.split()
            if tool.strip(",")
        ]
    else:
        if raw_tools is not None:
            logger.warning(
                "Ignoring non-string 'allowed-tools' in %s (got %s)",
                skill_path,
                type(raw_tools).__name__,
            )
        allowed_tools = []

    compatibility = str(frontmatter_data.get("compatibility", "")).strip() or None
    if compatibility and len(compatibility) > MAX_SKILL_COMPATIBILITY_LENGTH:
        logger.warning(
            "Compatibility exceeds %d characters in %s, truncating",
            MAX_SKILL_COMPATIBILITY_LENGTH,
            skill_path,
        )
        compatibility = compatibility[:MAX_SKILL_COMPATIBILITY_LENGTH]

    raw_version = frontmatter_data.get("version")
    version = 0
    if raw_version is not None:
        try:
            version = int(raw_version)
        except (ValueError, TypeError):
            logger.warning(
                "Invalid 'version' in %s (got %r), defaulting to 0",
                skill_path,
                raw_version,
            )

    return SkillMetadata(
        id=skill_id,
        name=name,
        version=version,
        description=description,
        path=skill_path,
        metadata=_validate_metadata(frontmatter_data.get("metadata", {}), skill_path),
        license=str(frontmatter_data.get("license", "")).strip() or None,
        compatibility=compatibility,
        allowed_tools=allowed_tools,
    )
