"""
Skill Validator - Validate skill definitions

Validates:
- Skill name format (1-64 chars, lowercase, hyphens)
- Description length (1-1024 chars)
- SKILL.md file existence
- No duplicate skill names across directories
"""

import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional

from groknroll.skills.parser import FrontmatterParser, ValidationError


@dataclass
class SkillValidationError:
    """
    Represents a single validation error

    Attributes:
        skill_name: Name of the skill (or directory name if name invalid)
        field: Field that failed validation (or "file" for file issues)
        message: Human-readable error message
        path: Path to the skill directory
    """

    skill_name: str
    field: str
    message: str
    path: Optional[Path] = None

    def __str__(self) -> str:
        if self.path:
            return f"[{self.skill_name}] {self.field}: {self.message} ({self.path})"
        return f"[{self.skill_name}] {self.field}: {self.message}"


@dataclass
class ValidationResult:
    """
    Result of validating one or more skills

    Attributes:
        valid: True if all validations passed
        errors: List of validation errors
        warnings: List of non-fatal warnings
        validated_count: Number of skills validated
    """

    valid: bool
    errors: list[SkillValidationError] = field(default_factory=list)
    warnings: list[str] = field(default_factory=list)
    validated_count: int = 0

    def add_error(
        self,
        skill_name: str,
        field_name: str,
        message: str,
        path: Optional[Path] = None,
    ) -> None:
        """Add a validation error"""
        self.errors.append(
            SkillValidationError(
                skill_name=skill_name,
                field=field_name,
                message=message,
                path=path,
            )
        )
        self.valid = False

    def add_warning(self, message: str) -> None:
        """Add a warning (doesn't affect validity)"""
        self.warnings.append(message)

    def merge(self, other: "ValidationResult") -> None:
        """Merge another result into this one"""
        self.errors.extend(other.errors)
        self.warnings.extend(other.warnings)
        self.validated_count += other.validated_count
        if not other.valid:
            self.valid = False


class SkillValidator:
    """
    Validate skill definitions

    Validates skill directories and SKILL.md files for:
    - Valid skill name format
    - Valid description
    - SKILL.md file existence
    - No duplicate skill names

    Example:
        validator = SkillValidator()

        # Validate a single skill directory
        result = validator.validate_skill(Path(".groknroll/skill/my-skill"))

        # Validate all skills in a directory
        result = validator.validate_directory(Path(".groknroll/skill"))

        if not result.valid:
            for error in result.errors:
                print(error)
    """

    SKILL_FILENAME = "SKILL.md"

    # Name pattern: lowercase alphanumeric with hyphens, no leading/trailing/consecutive hyphens
    NAME_PATTERN = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")

    def __init__(self):
        """Initialize validator"""
        self.parser = FrontmatterParser()
        self._seen_names: set[str] = set()

    def validate_skill(self, skill_dir: Path) -> ValidationResult:
        """
        Validate a single skill directory

        Args:
            skill_dir: Path to skill directory (containing SKILL.md)

        Returns:
            ValidationResult with any errors found
        """
        result = ValidationResult(valid=True, validated_count=1)
        dir_name = skill_dir.name

        # Check directory exists
        if not skill_dir.exists():
            result.add_error(dir_name, "directory", "does not exist", skill_dir)
            return result

        if not skill_dir.is_dir():
            result.add_error(dir_name, "directory", "is not a directory", skill_dir)
            return result

        # Check SKILL.md exists
        skill_file = skill_dir / self.SKILL_FILENAME
        if not skill_file.exists():
            result.add_error(dir_name, "file", f"{self.SKILL_FILENAME} not found", skill_dir)
            return result

        # Validate directory name as skill name
        name_errors = self._validate_name(dir_name)
        for error in name_errors:
            result.add_error(dir_name, "name", error, skill_dir)

        # Parse and validate SKILL.md content
        try:
            content = skill_file.read_text(encoding="utf-8")
            parse_result = self.parser.parse(content)

            # Check for parse errors
            for error in parse_result.errors:
                result.add_error(dir_name, "frontmatter", error, skill_file)

            # Validate description if present
            if parse_result.metadata.description:
                desc_errors = self._validate_description(parse_result.metadata.description)
                for error in desc_errors:
                    result.add_error(dir_name, "description", error, skill_file)

            # If frontmatter has a name, validate it matches directory
            if parse_result.metadata.name:
                if parse_result.metadata.name != dir_name:
                    result.add_warning(
                        f"[{dir_name}] frontmatter name '{parse_result.metadata.name}' "
                        f"differs from directory name '{dir_name}'"
                    )

        except UnicodeDecodeError as e:
            result.add_error(dir_name, "encoding", f"invalid UTF-8: {e}", skill_file)
        except OSError as e:
            result.add_error(dir_name, "file", f"cannot read: {e}", skill_file)

        return result

    def validate_directory(
        self,
        skills_dir: Path,
        check_duplicates: bool = True,
        reset_seen: bool = True,
    ) -> ValidationResult:
        """
        Validate all skills in a directory

        Args:
            skills_dir: Path to skills directory (e.g., .groknroll/skill/)
            check_duplicates: Whether to check for duplicate skill names
            reset_seen: Whether to reset seen names tracking (set False for cross-directory checks)

        Returns:
            ValidationResult with all errors found
        """
        result = ValidationResult(valid=True)

        if not skills_dir.exists():
            result.add_warning(f"Skills directory does not exist: {skills_dir}")
            return result

        if not skills_dir.is_dir():
            result.add_error("", "directory", "not a directory", skills_dir)
            return result

        # Reset duplicate tracking if checking and reset requested
        if check_duplicates and reset_seen:
            self._seen_names.clear()

        # Validate each subdirectory
        for item in sorted(skills_dir.iterdir()):
            if not item.is_dir():
                continue

            # Skip hidden directories
            if item.name.startswith("."):
                continue

            skill_result = self.validate_skill(item)
            result.merge(skill_result)

            # Check for duplicates
            if check_duplicates:
                if item.name in self._seen_names:
                    result.add_error(
                        item.name,
                        "duplicate",
                        f"skill name '{item.name}' already exists",
                        item,
                    )
                else:
                    self._seen_names.add(item.name)

        return result

    def validate_multiple_directories(
        self,
        directories: list[Path],
    ) -> ValidationResult:
        """
        Validate skills across multiple directories with duplicate checking

        Useful for checking both project and global skill directories.

        Args:
            directories: List of skill directories to validate

        Returns:
            ValidationResult with all errors (including cross-directory duplicates)
        """
        result = ValidationResult(valid=True)
        self._seen_names.clear()

        for i, directory in enumerate(directories):
            # Only reset on first directory, keep tracking across directories
            dir_result = self.validate_directory(
                directory,
                check_duplicates=True,
                reset_seen=(i == 0),
            )
            result.merge(dir_result)

        return result

    def validate_name(self, name: str) -> ValidationResult:
        """
        Validate a skill name

        Args:
            name: Skill name to validate

        Returns:
            ValidationResult
        """
        result = ValidationResult(valid=True, validated_count=1)
        errors = self._validate_name(name)
        for error in errors:
            result.add_error(name, "name", error)
        return result

    def _validate_name(self, name: str) -> list[str]:
        """
        Validate skill name format

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

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

        if not name:
            errors.append("cannot be empty")
            return errors

        if len(name) > 64:
            errors.append("must be 64 characters or less")

        if not self.NAME_PATTERN.match(name):
            errors.append(
                "must contain only lowercase letters, numbers, and hyphens (e.g., 'my-skill-name')"
            )

        return errors

    def _validate_description(self, description: str) -> list[str]:
        """
        Validate skill description

        Rules:
        - 1-1024 characters

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

        if len(description) > 1024:
            errors.append("must be 1024 characters or less")

        return errors

    def clear_seen_names(self) -> None:
        """Clear the set of seen names (for duplicate detection)"""
        self._seen_names.clear()


# Module-level convenience functions


def validate_skill(skill_dir: Path) -> ValidationResult:
    """Validate a single skill directory"""
    return SkillValidator().validate_skill(skill_dir)


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

    Args:
        name: Name to validate

    Returns:
        True if valid

    Raises:
        ValidationError: If invalid
    """
    validator = SkillValidator()
    result = validator.validate_name(name)
    if not result.valid:
        error = result.errors[0]
        raise ValidationError("name", error.message)
    return True
