"""YAML manifest parser for skills.

This module provides parsing and validation for skill manifests
written in YAML format.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from pathlib import Path

from oclawma.skills.base import SkillLoadError, ToolInfo


@dataclass
class SkillManifest:
    """Manifest for a skill loaded from YAML.

    This is the on-disk representation of a skill's metadata.

    Example YAML:
        name: docker
        version: 1.0.0
        description: Docker container management
        author: OpenClaw Team
        category: infrastructure
        tags: [containers, docker, devops]
        requirements:
          - docker>=6.0.0
        entry_point: docker_skill:DockerSkill
        tools:
          - name: ps
            description: List running containers
            parameters:
              - name: all
                type: boolean
                description: Show all containers
                required: false
            returns: string
            token_count: 150
    """

    name: str
    version: str
    description: str = ""
    author: str = ""
    category: str = "general"
    tags: list[str] = field(default_factory=list)
    requirements: list[str] = field(default_factory=list)
    entry_point: str = ""  # Python module path or file path
    tools: list[ToolInfo] = field(default_factory=list)

    @classmethod
    def from_yaml(cls, path: Path) -> SkillManifest:
        """Load a manifest from a YAML file.

        Args:
            path: Path to the YAML manifest file

        Returns:
            Parsed SkillManifest

        Raises:
            SkillLoadError: If the manifest is invalid
        """
        import yaml

        try:
            with open(path, encoding="utf-8") as f:
                data = yaml.safe_load(f)
        except Exception as e:
            raise SkillLoadError(f"Failed to load manifest from {path}: {e}", cause=e) from e

        if not isinstance(data, dict):
            raise SkillLoadError(
                f"Invalid manifest format in {path}: expected dict, got {type(data).__name__}"
            )

        # Validate required fields
        if "name" not in data:
            raise SkillLoadError(f"Manifest {path} missing required field: name")

        # Parse tools
        tools = []
        for tool_data in data.get("tools", []):
            if not isinstance(tool_data, dict):
                raise SkillLoadError(
                    f"Invalid tool definition in {path}: expected dict, got {type(tool_data).__name__}"
                )

            tools.append(
                ToolInfo(
                    name=tool_data["name"],
                    description=tool_data.get("description", ""),
                    parameters=tool_data.get("parameters", []),
                    returns=tool_data.get("returns", "string"),
                    token_count=tool_data.get("token_count", 0),
                )
            )

        return cls(
            name=data["name"],
            version=data.get("version", "0.1.0"),
            description=data.get("description", ""),
            author=data.get("author", ""),
            category=data.get("category", "general"),
            tags=data.get("tags", []),
            requirements=data.get("requirements", []),
            entry_point=data.get("entry_point", ""),
            tools=tools,
        )

    def to_yaml(self, path: Path) -> None:
        """Save the manifest to a YAML file.

        Args:
            path: Path to save the YAML file
        """
        import yaml

        data = {
            "name": self.name,
            "version": self.version,
            "description": self.description,
            "author": self.author,
            "category": self.category,
            "tags": self.tags,
            "requirements": self.requirements,
            "entry_point": self.entry_point,
            "tools": [tool.to_dict() for tool in self.tools],
        }

        path.parent.mkdir(parents=True, exist_ok=True)
        with open(path, "w", encoding="utf-8") as f:
            yaml.dump(data, f, default_flow_style=False, sort_keys=False)

    def to_metadata(self, manifest_path: Path | None = None) -> SkillMetadata:  # noqa: F821
        """Convert to SkillMetadata.

        Args:
            manifest_path: Optional path to the manifest file

        Returns:
            SkillMetadata with information from this manifest
        """
        from oclawma.skills.base import SkillMetadata

        # Calculate estimated tokens
        estimated_tokens = sum(tool.token_count for tool in self.tools)
        # Add overhead for skill metadata (rough estimate: 1 token per 4 chars)
        estimated_tokens += len(self.description) // 4
        estimated_tokens += len(self.name) // 4
        estimated_tokens += 50  # Base overhead

        return SkillMetadata(
            name=self.name,
            version=self.version,
            description=self.description,
            author=self.author,
            category=self.category,
            tags=self.tags,
            requirements=self.requirements,
            entry_point=self.entry_point,
            manifest_path=manifest_path,
            estimated_tokens=estimated_tokens,
            tool_count=len(self.tools),
        )

    def validate(self) -> list[str]:
        """Validate the manifest and return any errors.

        Returns:
            List of validation error messages (empty if valid)
        """
        errors = []

        if not self.name:
            errors.append("Skill name is required")

        if not self.version:
            errors.append("Skill version is required")

        if not self.entry_point:
            errors.append("Entry point is required")

        # Validate tool names are unique
        tool_names = [t.name for t in self.tools]
        if len(tool_names) != len(set(tool_names)):
            errors.append("Duplicate tool names found")

        return errors


class ManifestParser:
    """Parser for skill manifests with caching support."""

    def __init__(self) -> None:
        """Initialize the parser."""
        self._cache: dict[Path, SkillManifest] = {}

    def parse(self, path: Path, use_cache: bool = True) -> SkillManifest:
        """Parse a manifest file.

        Args:
            path: Path to the YAML manifest
            use_cache: Whether to use cached results

        Returns:
            Parsed SkillManifest
        """
        path = path.resolve()

        if use_cache and path in self._cache:
            return self._cache[path]

        manifest = SkillManifest.from_yaml(path)

        if use_cache:
            self._cache[path] = manifest

        return manifest

    def clear_cache(self) -> None:
        """Clear the parse cache."""
        self._cache.clear()

    def invalidate(self, path: Path) -> None:
        """Invalidate a cached manifest."""
        path = path.resolve()
        self._cache.pop(path, None)
