"""Command registry for slash command management."""

import logging
from typing import Dict, List, Optional, Tuple
from collections import defaultdict

from ..events.models import CommandDefinition, CommandCategory

logger = logging.getLogger(__name__)


class SlashCommandRegistry:
    """Registry for managing slash command definitions.

    Handles command registration, conflict detection, and organization
    by plugin and category for efficient discovery and execution.
    """

    def __init__(self) -> None:
        """Initialize the command registry."""
        self._commands: Dict[str, CommandDefinition] = {}
        self._aliases: Dict[str, str] = {}  # alias -> command_name mapping
        self._plugin_commands: Dict[str, List[str]] = defaultdict(list)
        self._category_commands: Dict[CommandCategory, List[str]] = defaultdict(list)
        self.logger = logger

    def register_command(self, command_def: CommandDefinition) -> bool:
        """Register a slash command with the registry.

        Args:
            command_def: Command definition to register.

        Returns:
            True if registration successful, False if conflicts exist.
        """
        try:
            # Validate command definition
            validation_errors = self._validate_command_definition(command_def)
            if validation_errors:
                self.logger.error(f"Command validation failed for {command_def.name}: {validation_errors}")
                return False

            # Check for name conflicts
            if command_def.name in self._commands:
                existing = self._commands[command_def.name]
                self.logger.error(
                    f"Command name conflict: '{command_def.name}' already registered by plugin '{existing.plugin_name}'"
                )
                return False

            # Check for alias conflicts
            for alias in command_def.aliases:
                if alias in self._commands or alias in self._aliases:
                    self.logger.error(f"Alias conflict: '{alias}' already in use")
                    return False

            # Register the command
            self._commands[command_def.name] = command_def

            # Register aliases
            for alias in command_def.aliases:
                self._aliases[alias] = command_def.name

            # Track by plugin
            self._plugin_commands[command_def.plugin_name].append(command_def.name)

            # Track by category
            self._category_commands[command_def.category].append(command_def.name)

            self.logger.info(
                f"Registered command '{command_def.name}' from plugin '{command_def.plugin_name}' "
                f"with {len(command_def.aliases)} aliases"
            )
            return True

        except Exception as e:
            self.logger.error(f"Error registering command '{command_def.name}': {e}")
            return False

    def unregister_command(self, command_name: str) -> bool:
        """Unregister a command and its aliases.

        Args:
            command_name: Name of command to unregister.

        Returns:
            True if successfully unregistered, False if not found.
        """
        if command_name not in self._commands:
            return False

        try:
            command_def = self._commands[command_name]

            # Remove from main registry
            del self._commands[command_name]

            # Remove aliases
            for alias in command_def.aliases:
                if alias in self._aliases:
                    del self._aliases[alias]

            # Remove from plugin tracking
            if command_def.plugin_name in self._plugin_commands:
                plugin_commands = self._plugin_commands[command_def.plugin_name]
                if command_name in plugin_commands:
                    plugin_commands.remove(command_name)

            # Remove from category tracking
            category_commands = self._category_commands[command_def.category]
            if command_name in category_commands:
                category_commands.remove(command_name)

            self.logger.info(f"Unregistered command '{command_name}'")
            return True

        except Exception as e:
            self.logger.error(f"Error unregistering command '{command_name}': {e}")
            return False

    def unregister_plugin_commands(self, plugin_name: str) -> int:
        """Unregister all commands from a specific plugin.

        Args:
            plugin_name: Name of plugin whose commands to unregister.

        Returns:
            Number of commands unregistered.
        """
        if plugin_name not in self._plugin_commands:
            return 0

        # Get list of commands to unregister (copy to avoid modification during iteration)
        commands_to_remove = list(self._plugin_commands[plugin_name])

        unregistered_count = 0
        for command_name in commands_to_remove:
            if self.unregister_command(command_name):
                unregistered_count += 1

        # Clean up empty plugin entry
        if plugin_name in self._plugin_commands and not self._plugin_commands[plugin_name]:
            del self._plugin_commands[plugin_name]

        self.logger.info(f"Unregistered {unregistered_count} commands from plugin '{plugin_name}'")
        return unregistered_count

    def get_command(self, name_or_alias: str) -> Optional[CommandDefinition]:
        """Get command definition by name or alias.

        Args:
            name_or_alias: Command name or alias to look up.

        Returns:
            Command definition if found, None otherwise.
        """
        # Direct name lookup
        if name_or_alias in self._commands:
            return self._commands[name_or_alias]

        # Alias lookup
        if name_or_alias in self._aliases:
            command_name = self._aliases[name_or_alias]
            return self._commands.get(command_name)

        return None

    def list_commands(self, include_hidden: bool = False) -> List[CommandDefinition]:
        """List all registered commands.

        Args:
            include_hidden: Whether to include hidden commands.

        Returns:
            List of command definitions.
        """
        commands = list(self._commands.values())

        if not include_hidden:
            commands = [cmd for cmd in commands if not cmd.hidden]

        # Sort by category, then by name
        return sorted(commands, key=lambda cmd: (cmd.category.value, cmd.name))

    def get_commands_by_plugin(self, plugin_name: str) -> List[CommandDefinition]:
        """Get all commands registered by a specific plugin.

        Args:
            plugin_name: Name of plugin to get commands for.

        Returns:
            List of command definitions from the plugin.
        """
        if plugin_name not in self._plugin_commands:
            return []

        command_names = self._plugin_commands[plugin_name]
        return [self._commands[name] for name in command_names if name in self._commands]

    def get_commands_by_category(self, category: CommandCategory) -> List[CommandDefinition]:
        """Get all commands in a specific category.

        Args:
            category: Category to get commands for.

        Returns:
            List of command definitions in the category.
        """
        if category not in self._category_commands:
            return []

        command_names = self._category_commands[category]
        return [self._commands[name] for name in command_names if name in self._commands]

    def get_plugin_categories(self) -> Dict[str, List[CommandCategory]]:
        """Get categories used by each plugin.

        Returns:
            Dictionary mapping plugin names to their used categories.
        """
        plugin_categories = defaultdict(set)

        for command_def in self._commands.values():
            plugin_categories[command_def.plugin_name].add(command_def.category)

        # Convert sets to sorted lists
        return {
            plugin: sorted(categories, key=lambda c: c.value)
            for plugin, categories in plugin_categories.items()
        }

    def search_commands(self, query: str) -> List[CommandDefinition]:
        """Search commands by name, description, or aliases.

        Args:
            query: Search query string.

        Returns:
            List of matching command definitions.
        """
        query_lower = query.lower()
        matches = []

        for command_def in self._commands.values():
            # Skip hidden commands in search
            if command_def.hidden:
                continue

            # Match name
            if query_lower in command_def.name.lower():
                matches.append(command_def)
                continue

            # Match description
            if query_lower in command_def.description.lower():
                matches.append(command_def)
                continue

            # Match aliases
            if any(query_lower in alias.lower() for alias in command_def.aliases):
                matches.append(command_def)
                continue

        return sorted(matches, key=lambda cmd: cmd.name)

    def get_registry_stats(self) -> Dict[str, int]:
        """Get registry statistics for debugging.

        Returns:
            Dictionary with registry statistics.
        """
        return {
            "total_commands": len(self._commands),
            "total_aliases": len(self._aliases),
            "plugins": len(self._plugin_commands),
            "categories": len([cat for cat in self._category_commands if self._category_commands[cat]]),
            "hidden_commands": len([cmd for cmd in self._commands.values() if cmd.hidden]),
            "enabled_commands": len([cmd for cmd in self._commands.values() if cmd.enabled])
        }

    def _validate_command_definition(self, command_def: CommandDefinition) -> List[str]:
        """Validate a command definition for correctness.

        Args:
            command_def: Command definition to validate.

        Returns:
            List of validation errors, empty if valid.
        """
        errors = []

        # Validate name
        if not command_def.name:
            errors.append("Command name cannot be empty")
        elif not command_def.name.replace('-', '').replace('_', '').isalnum():
            errors.append(f"Invalid command name format: {command_def.name}")

        # Validate description
        if not command_def.description:
            errors.append("Command description cannot be empty")

        # Validate handler
        if not callable(command_def.handler):
            errors.append("Command handler must be callable")

        # Validate plugin name
        if not command_def.plugin_name:
            errors.append("Plugin name cannot be empty")

        # Validate aliases (allow special characters like ?)
        for alias in command_def.aliases:
            if not alias or len(alias.strip()) == 0:
                errors.append(f"Empty alias not allowed")
            elif not (alias.replace('-', '').replace('_', '').isalnum() or alias in ['?']):
                errors.append(f"Invalid alias format: {alias}")

        return errors