"""
YAML Frontmatter Parser for Skill files

Parses YAML frontmatter from SKILL.md files with validation.

Frontmatter format:
```markdown
---
name: skill-name
description: What the skill does
license: MIT
compatibility: ">=1.0.0"
metadata:
  author: John Doe
  version: 1.0.0
---

# Skill Content Here
```
"""

import re
from dataclasses import dataclass, field
from typing import Any, Optional

import yaml


class FrontmatterError(Exception):
    """Error parsing or validating frontmatter"""

    pass


class ValidationError(FrontmatterError):
    """Frontmatter validation error"""

    def __init__(self, field: str, message: str):
        self.field = field
        self.message = message
        super().__init__(f"Validation error for '{field}': {message}")


@dataclass
class SkillMetadata:
    """
    Parsed and validated skill metadata from YAML frontmatter

    Attributes:
        name: Skill identifier (1-64 chars, lowercase, hyphens)
        description: Human-readable description (1-1024 chars)
        license: Optional license identifier (e.g., "MIT", "Apache-2.0")
        compatibility: Optional version constraint (e.g., ">=1.0.0")
        metadata: Additional custom metadata
        raw: Raw parsed YAML dict
    """

    name: Optional[str] = None
    description: str = ""
    license: Optional[str] = None
    compatibility: Optional[str] = None
    metadata: dict[str, Any] = field(default_factory=dict)
    raw: dict[str, Any] = field(default_factory=dict)

    def __post_init__(self):
        """Validate fields after initialization"""
        # Name validation (if provided)
        if self.name is not None:
            self._validate_name(self.name)

        # Description validation
        self._validate_description(self.description)

    @staticmethod
    def _validate_name(name: str) -> None:
        """
        Validate skill name

        Rules:
        - 1-64 characters
        - Lowercase letters, numbers, hyphens only
        - Cannot start or end with hyphen
        - No consecutive hyphens

        Raises:
            ValidationError: If name is invalid
        """
        if not name:
            raise ValidationError("name", "cannot be empty")

        if len(name) > 64:
            raise ValidationError("name", "must be 64 characters or less")

        # Pattern: lowercase alphanumeric, hyphens allowed but not at start/end
        pattern = r"^[a-z0-9]+(-[a-z0-9]+)*$"
        if not re.match(pattern, name):
            raise ValidationError(
                "name",
                "must be lowercase alphanumeric with hyphens (e.g., 'my-skill-name')",
            )

    @staticmethod
    def _validate_description(description: str) -> None:
        """
        Validate skill description

        Rules:
        - 0-1024 characters (empty allowed, will use default)

        Raises:
            ValidationError: If description is invalid
        """
        if len(description) > 1024:
            raise ValidationError("description", "must be 1024 characters or less")


@dataclass
class ParseResult:
    """
    Result of parsing frontmatter

    Attributes:
        metadata: Parsed SkillMetadata
        content: Content after frontmatter (without the --- markers)
        has_frontmatter: Whether frontmatter was found
        errors: List of non-fatal parsing warnings
    """

    metadata: SkillMetadata
    content: str
    has_frontmatter: bool
    errors: list[str] = field(default_factory=list)


class FrontmatterParser:
    """
    Parse YAML frontmatter from skill files

    Extracts and validates YAML frontmatter between --- markers.
    Handles malformed YAML gracefully with error reporting.

    Example:
        parser = FrontmatterParser()
        result = parser.parse(content)

        if result.has_frontmatter:
            print(f"Name: {result.metadata.name}")
            print(f"Description: {result.metadata.description}")
        print(f"Content: {result.content}")
    """

    # Regex to match frontmatter block
    # Handles both empty frontmatter (---\n---) and content with newlines
    FRONTMATTER_PATTERN = re.compile(
        r"^---[ \t]*\n(.*?)(?:\n|^)---[ \t]*(?:\n|$)",
        re.DOTALL | re.MULTILINE,
    )

    def parse(self, content: str) -> ParseResult:
        """
        Parse frontmatter from content

        Args:
            content: Full file content (with or without frontmatter)

        Returns:
            ParseResult with metadata, content, and any errors

        Example:
            result = parser.parse('''---
            name: my-skill
            description: Does something
            ---
            # Content here
            ''')
        """
        errors: list[str] = []
        metadata = SkillMetadata()

        # Check for frontmatter
        match = self.FRONTMATTER_PATTERN.match(content)

        if not match:
            # No frontmatter found
            return ParseResult(
                metadata=metadata,
                content=content,
                has_frontmatter=False,
                errors=errors,
            )

        # Extract YAML and remaining content
        yaml_content = match.group(1)
        remaining_content = content[match.end() :]

        # Parse YAML
        try:
            parsed = yaml.safe_load(yaml_content)
        except yaml.YAMLError as e:
            errors.append(f"YAML parse error: {e}")
            return ParseResult(
                metadata=metadata,
                content=remaining_content,
                has_frontmatter=True,
                errors=errors,
            )

        # Handle empty or non-dict YAML
        if parsed is None:
            return ParseResult(
                metadata=metadata,
                content=remaining_content,
                has_frontmatter=True,
                errors=errors,
            )

        if not isinstance(parsed, dict):
            errors.append(f"Frontmatter must be a YAML mapping, got {type(parsed).__name__}")
            return ParseResult(
                metadata=metadata,
                content=remaining_content,
                has_frontmatter=True,
                errors=errors,
            )

        # Build metadata with validation
        try:
            metadata = self._build_metadata(parsed, errors)
        except ValidationError as e:
            errors.append(str(e))
            # Return partial metadata on validation error
            metadata = SkillMetadata(raw=parsed)

        return ParseResult(
            metadata=metadata,
            content=remaining_content,
            has_frontmatter=True,
            errors=errors,
        )

    def parse_strict(self, content: str) -> ParseResult:
        """
        Parse frontmatter with strict validation

        Like parse(), but raises ValidationError on any validation failure.

        Args:
            content: Full file content

        Returns:
            ParseResult

        Raises:
            ValidationError: If validation fails
            FrontmatterError: If YAML parsing fails
        """
        result = self.parse(content)

        if result.errors:
            raise FrontmatterError("; ".join(result.errors))

        return result

    def _build_metadata(self, parsed: dict[str, Any], errors: list[str]) -> SkillMetadata:
        """
        Build SkillMetadata from parsed YAML dict

        Args:
            parsed: Parsed YAML dictionary
            errors: List to append non-fatal errors to

        Returns:
            SkillMetadata instance
        """
        # Extract known fields
        name = parsed.get("name")
        description = parsed.get("description", "")
        license_field = parsed.get("license")
        compatibility = parsed.get("compatibility")
        extra_metadata = parsed.get("metadata", {})

        # Ensure types
        if name is not None and not isinstance(name, str):
            errors.append(f"'name' must be a string, got {type(name).__name__}")
            name = str(name)

        if not isinstance(description, str):
            errors.append(f"'description' must be a string, got {type(description).__name__}")
            description = str(description) if description else ""

        if license_field is not None and not isinstance(license_field, str):
            errors.append(f"'license' must be a string, got {type(license_field).__name__}")
            license_field = str(license_field)

        if compatibility is not None and not isinstance(compatibility, str):
            errors.append(f"'compatibility' must be a string, got {type(compatibility).__name__}")
            compatibility = str(compatibility)

        if not isinstance(extra_metadata, dict):
            errors.append(f"'metadata' must be a mapping, got {type(extra_metadata).__name__}")
            extra_metadata = {}

        return SkillMetadata(
            name=name,
            description=description,
            license=license_field,
            compatibility=compatibility,
            metadata=extra_metadata,
            raw=parsed,
        )

    def extract_frontmatter(self, content: str) -> tuple[Optional[str], str]:
        """
        Extract raw frontmatter YAML and content

        Args:
            content: Full file content

        Returns:
            Tuple of (frontmatter_yaml or None, remaining_content)
        """
        match = self.FRONTMATTER_PATTERN.match(content)

        if not match:
            return None, content

        return match.group(1), content[match.end() :]

    def has_frontmatter(self, content: str) -> bool:
        """
        Check if content has frontmatter

        Args:
            content: File content to check

        Returns:
            True if frontmatter is present
        """
        return bool(self.FRONTMATTER_PATTERN.match(content))


# Module-level convenience functions


def parse_frontmatter(content: str) -> ParseResult:
    """
    Parse frontmatter from content (convenience function)

    Args:
        content: File content

    Returns:
        ParseResult
    """
    return FrontmatterParser().parse(content)


def validate_skill_name(name: str) -> bool:
    """
    Validate a skill name

    Args:
        name: Name to validate

    Returns:
        True if valid

    Raises:
        ValidationError: If invalid
    """
    SkillMetadata._validate_name(name)
    return True
