import inspect
from typing import Any

"""Multi-class FreyjaCLI command handling and collision detection.

Provides services for managing cmd_tree from multiple classes in a single FreyjaCLI,
including collision detection, command ordering, and source tracking.
"""


class ClassHandler:
    """Handles cmd_tree from multiple classes with collision detection and ordering."""

    def __init__(self):
        """Initialize multi-class handler."""
        self.command_sources: dict[str, type] = {}  # command_name -> source_class
        self.class_commands: dict[type, list[str]] = {}  # source_class -> [command_names]
        self.collision_tracker: dict[str, list[type]] = {}  # command_name -> [source_classes]

    def track_command(self, command_name: str, source_class: type) -> None:
        """
        Track a command and its source class for collision detection.

        :param command_name: FreyjaCLI command name (e.g., 'file-operations--process-single')
        :param source_class: Source class that defines this command
        """
        # Track which class this command comes from
        if command_name in self.command_sources:
            # Collision detected - track all sources
            if command_name not in self.collision_tracker:
                self.collision_tracker[command_name] = [self.command_sources[command_name]]
            self.collision_tracker[command_name].append(source_class)
        else:
            self.command_sources[command_name] = source_class

        # Track cmd_tree per class for ordering
        if source_class not in self.class_commands:
            self.class_commands[source_class] = []
        self.class_commands[source_class].append(command_name)

    def detect_collisions(self) -> list[tuple[str, list[type]]]:
        """
        Detect and return command name collisions.

        :return: List of (command_name, [conflicting_classes]) tuples
        """
        return list(self.collision_tracker.items())

    def has_collisions(self) -> bool:
        """
        Check if any command name collisions exist.

        :return: True if collisions detected, False otherwise
        """
        return len(self.collision_tracker) > 0

    def get_ordered_commands(self, class_order: list[type]) -> list[str]:
        """
        Get cmd_tree ordered by class sequence, then alphabetically within each class.

        :param class_order: Desired order of classes
        :return: List of command names in proper order
        """
        ordered_commands = []

        # Process classes in the specified order
        for cls in class_order:
            if cls in self.class_commands:
                # Sort cmd_tree within this class alphabetically
                class_commands = sorted(self.class_commands[cls])
                ordered_commands.extend(class_commands)

        return ordered_commands

    def get_command_source(self, command_name: str) -> type | None:
        """
        Get the source class for a command.

        :param command_name: FreyjaCLI command name
        :return: Source class or None if not found
        """
        return self.command_sources.get(command_name)

    def format_collision_error(self) -> str:
        """
        Format collision error message for user display.

        :return: Formatted error message describing all collisions
        """
        if not self.has_collisions():
            return ""

        error_lines = ["Command name collisions detected:"]

        for command_name, conflicting_classes in self.collision_tracker.items():
            class_names = [cls.__name__ for cls in conflicting_classes]
            error_lines.append(f"  '{command_name}' conflicts between: {', '.join(class_names)}")

        error_lines.append("")
        error_lines.append("Solutions:")
        error_lines.append("1. Rename methods in one of the conflicting classes")
        error_lines.append("2. Use different inner class names to create unique command paths")
        error_lines.append("3. Use separate FreyjaCLI instances for conflicting classes")

        return "\n".join(error_lines)

    def validate_classes(self, classes: list[type]) -> None:
        """Validate that classes can be used together without collisions.

        :param classes: List of classes to validate
        :raises ValueError: If command collisions are detected"""
        # Simulate command discovery to detect collisions
        temp_handler = ClassHandler()

        for cls in classes:
            # Simulate the command discovery process
            self._simulate_class_commands(temp_handler, cls)

        # Check for collisions
        if temp_handler.has_collisions():
            raise ValueError(temp_handler.format_collision_error())

    def _simulate_class_commands(self, handler: "ClassHandler", cls: type) -> None:
        """Simulate command discovery for collision detection.

        :param handler: Handler to track cmd_tree in
        :param cls: Class to simulate cmd_tree for"""
        from freyja.utils.text_util import TextUtil

        # Check for inner classes (hierarchical cmd_tree)
        inner_classes = self._discover_inner_classes(cls)

        if inner_classes:
            # Inner class pattern - track both direct methods and inner class methods
            # Direct methods
            for name, obj in inspect.getmembers(cls):
                if self._is_valid_method(name, obj, cls):
                    cli_name = TextUtil.kebab_case(name)
                    handler.track_command(cli_name, cls)

            # Inner class methods
            for class_name, inner_class in inner_classes.items():
                command_name = TextUtil.kebab_case(class_name)

                for method_name, method_obj in inspect.getmembers(inner_class):
                    if (
                        not method_name.startswith("_")
                        and callable(method_obj)
                        and method_name != "__init__"
                        and inspect.isfunction(method_obj)
                    ):
                        # Create hierarchical command name
                        cli_name = f"{command_name}--{TextUtil.kebab_case(method_name)}"
                        handler.track_command(cli_name, cls)
        else:
            # Direct methods only
            for name, obj in inspect.getmembers(cls):
                if self._is_valid_method(name, obj, cls):
                    cli_name = TextUtil.kebab_case(name)
                    handler.track_command(cli_name, cls)

    def _discover_inner_classes(self, cls: type) -> dict[str, type]:
        """Discover inner classes for a given class.

        :param cls: Class to check for inner classes
        :return: Dictionary of inner class name -> inner class"""
        inner_classes = {}

        for name, obj in inspect.getmembers(cls):
            if (
                inspect.isclass(obj)
                and not name.startswith("_")
                and obj.__qualname__.endswith(f"{cls.__name__}.{name}")
            ):
                inner_classes[name] = obj

        return inner_classes

    def _is_valid_method(self, name: str, obj: Any, cls: type) -> bool:
        """Check if a method should be included as a FreyjaCLI command.

        :param name: Method name
        :param obj: Method object
        :param cls: Containing class
        :return: True if method should be included"""
        return (
            not name.startswith("_")
            and callable(obj)
            and (inspect.isfunction(obj) or inspect.ismethod(obj))
            and hasattr(obj, "__qualname__")
            and cls.__name__ in obj.__qualname__
        )
