"""Skill Registry - Single source of truth for skill management.

This module provides the centralized skill registry that was previously
scattered across multiple locations.

Usage:
    from core.skills import SkillRegistry, get_skill_registry

    # Get global registry
    registry = get_skill_registry()

    # Register a skill
    registry.register(skill)

    # Get a skill
    skill = registry.get("research")

    # Get instructions for tools
    instructions = registry.get_instructions_for_tools(["web.search", "web.fetch"])
"""

import fnmatch
import logging
from collections.abc import Iterator

from core.skills.models import Skill

logger = logging.getLogger(__name__)


class SkillRegistry:
    """Centralized registry for all skills.

    Thread-safe skill storage with lookup capabilities.

    Features:
    - Register/unregister skills
    - Lookup by ID or tag
    - Get instructions for tool patterns
    - List all skills
    """

    def __init__(self):
        self._skills: dict[str, Skill] = {}
        self._by_tag: dict[str, set[str]] = {}  # tag -> skill_ids

    def __len__(self) -> int:
        """Return number of registered skills."""
        return len(self._skills)

    def __contains__(self, skill_id: str) -> bool:
        """Check if a skill is registered."""
        return skill_id in self._skills

    def __iter__(self) -> Iterator[str]:
        """Iterate over skill IDs."""
        return iter(self._skills)

    def values(self) -> Iterator[Skill]:
        """Return skill values (for dict-like iteration)."""
        return self._skills.values()

    def items(self) -> Iterator[tuple[str, Skill]]:
        """Return skill items (for dict-like iteration)."""
        return self._skills.items()

    def keys(self) -> Iterator[str]:
        """Return skill IDs (for dict-like iteration)."""
        return self._skills.keys()

    def register(self, skill: Skill) -> None:
        """Register a skill.

        Args:
            skill: The skill to register
        """
        if skill.id in self._skills:
            logger.debug(f"Overwriting existing skill: {skill.id}")

        self._skills[skill.id] = skill

        # Index by tags
        for tag in skill.tags:
            if tag not in self._by_tag:
                self._by_tag[tag] = set()
            self._by_tag[tag].add(skill.id)

        logger.debug(f"Registered skill: {skill.id}")

    def unregister(self, skill_id: str) -> bool:
        """Unregister a skill.

        Args:
            skill_id: ID of the skill to remove

        Returns
        -------
            True if skill was removed, False if not found
        """
        skill = self._skills.pop(skill_id, None)
        if skill is None:
            return False

        # Remove from tag index
        for tag in skill.tags:
            if tag in self._by_tag:
                self._by_tag[tag].discard(skill_id)

        logger.debug(f"Unregistered skill: {skill_id}")
        return True

    def get(self, skill_id: str) -> Skill | None:
        """Get a skill by ID.

        Args:
            skill_id: The skill ID

        Returns
        -------
            The skill or None if not found
        """
        return self._skills.get(skill_id)

    def list(self) -> list[Skill]:
        """List all registered skills.

        Returns
        -------
            List of all skills
        """
        return list(self._skills.values())

    def list_ids(self) -> "list[str]":
        """List all skill IDs.

        Returns
        -------
            List of skill IDs
        """
        return list(self._skills.keys())

    def get_by_tag(self, tag: str) -> "list[Skill]":
        """Get skills by tag.

        Args:
            tag: The tag to search for

        Returns
        -------
            List of skills with that tag
        """
        skill_ids = self._by_tag.get(tag, set())
        return [self._skills[sid] for sid in skill_ids if sid in self._skills]

    def match_triggers(self, query: str) -> "list[Skill]":
        """Return skills whose use_when conditions match the query.

        Matching uses case-insensitive substring search for simplicity.
        Skills with auto_activate=True are always included.

        Args:
            query: The user's input query

        Returns
        -------
            List of skills that should be activated for this query
        """
        matched: list[Skill] = []
        query_lower = query.lower()

        for skill in self._skills.values():
            # Always include auto-activate skills
            if skill.auto_activate:
                matched.append(skill)
                continue

            # Check use_when triggers
            for trigger in skill.use_when:
                if trigger.lower() in query_lower:
                    matched.append(skill)
                    break

        return matched

    def get_instructions_for_tools(self, tool_patterns: "list[str]") -> str:
        """Get combined instructions for skills that provide given tools.

        This is used to inject relevant skill instructions into the system prompt
        based on which tools the agent has access to.

        Args:
            tool_patterns: List of tool patterns (e.g., ["web.*", "memory.search"])

        Returns
        -------
            Combined instructions from relevant skills
        """
        relevant_skills: set[str] = set()

        for skill in self._skills.values():
            # Check if any of the skill's tools match the patterns
            skill_tool_ids = skill.get_tool_ids()
            for tool_id in skill_tool_ids:
                for pattern in tool_patterns:
                    if fnmatch.fnmatch(tool_id, pattern):
                        relevant_skills.add(skill.id)
                        break

            # Also check by skill ID prefix (e.g., "web.*" matches skill "web")
            for pattern in tool_patterns:
                if pattern.endswith(".*"):
                    prefix = pattern[:-2]
                    if skill.id == prefix:
                        relevant_skills.add(skill.id)

        if not relevant_skills:
            return ""

        # Combine instructions
        sections = []
        for skill_id in sorted(relevant_skills):
            skill = self._skills.get(skill_id)
            if skill and skill.instructions:
                sections.append(skill.to_prompt_section())

        return "\n\n".join(sections)

    def to_dict(self) -> dict[str, dict[str, object]]:
        """Serialize registry to dict.

        Returns
        -------
            Dict mapping skill_id to skill.model_dump()
        """
        return {sid: skill.model_dump(mode="json") for sid, skill in self._skills.items()}

    def clear(self) -> None:
        """Clear all registered skills."""
        self._skills.clear()
        self._by_tag.clear()


# Global registry instance
_global_registry: SkillRegistry | None = None


def get_skill_registry() -> SkillRegistry:
    """Get the global skill registry.

    Typically set at bootstrap.  Lazily created if called
    before initialisation.
    """
    global _global_registry
    if _global_registry is None:
        _global_registry = SkillRegistry()
    return _global_registry


def set_skill_registry(registry: SkillRegistry) -> None:
    """Install *registry* as the global skill registry.

    Called at bootstrap to ensure the caller owns the instance.
    """
    global _global_registry
    _global_registry = registry


__all__ = [
    "SkillRegistry",
    "get_skill_registry",
    "set_skill_registry",
]
