"""Command tree structure for FreyjaCLI applications.

Provides a hierarchical command structure with convenience methods for efficient lookups.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any

from freyja.utils import TextUtil

from .command_info import CommandInfo


@dataclass
class CommandTree:
    """
    Hierarchical command tree structure with convenience methods.

    Encapsulates the command tree dictionary and provides efficient lookup methods
    for cmd_tree, groups, and source classes.
    """

    tree: dict[str, dict[str, Any]] = field(default_factory=dict)
    _command_info_lookup: dict[str, CommandInfo] = field(default_factory=dict, init=False)

    def __post_init__(self):
        """Initialize lookup structures after creation."""
        self._build_lookup_tables()

    # def __str__(self) -> str:
    #   """Format command tree"""
    #   print("GEtgingins itQ!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
    #   return TextUtil.format_pretty("CommandTree: {tree}", tree=str(self.tree))

    def _build_lookup_tables(self):
        """Build internal lookup tables for efficient access."""
        self._command_info_lookup.clear()
        self._build_command_lookup(self.tree)

    def _build_command_lookup(self, tree_dict: dict[str, Any], prefix: str = ""):
        """Recursively build command lookup table."""
        for name, info in tree_dict.items():
            full_name = f"{prefix}{name}" if prefix else name

            if info.get("type") == "command" and "command_info" in info:
                command_info = info["command_info"]
                self._command_info_lookup[full_name] = command_info
                # Also add original name for lookup
                if original_name := info.get("original_name"):
                    self._command_info_lookup[original_name] = command_info

            elif info.get("type") == "group" and "cmd_tree" in info:
                # Recursively process group cmd_tree
                self._build_command_lookup(info["cmd_tree"], f"{name} ")

    def add_command(self, name: str, command_info: CommandInfo, **kwargs):
        """Add a flat command to the tree."""
        self.tree[name] = {
            "type": "command",
            "function": command_info.function,
            "original_name": command_info.original_name,
            "command_info": command_info,
            **kwargs,
        }
        # Update lookup table
        self._command_info_lookup[name] = command_info
        if command_info.original_name != name:
            self._command_info_lookup[command_info.original_name] = command_info

    def add_group(
        self, group_name: str, description: str, inner_class: type | None = None, **kwargs
    ):
        """Add a command group to the tree."""
        self.tree[group_name] = {
            "type": "group",
            "description": description,
            "cmd_tree": {},
            "inner_class": inner_class,
            **kwargs,
        }

    def add_command_to_group(
        self, group_name: str, command_name: str, command_info: CommandInfo, **kwargs
    ):
        """Add a command to a specific group."""
        if group_name not in self.tree:
            raise ValueError(f"Group '{group_name}' not found")

        if self.tree[group_name].get("type") != "group":
            raise ValueError(f"'{group_name}' is not a group")

        self.tree[group_name]["cmd_tree"][command_name] = {
            "type": "command",
            "function": command_info.function,
            "original_name": command_info.original_name,
            "group_name": group_name,
            "command_info": command_info,
            **kwargs,
        }

        # Update lookup table with full path
        full_name = f"{group_name} {command_name}"
        self._command_info_lookup[full_name] = command_info
        self._command_info_lookup[command_name] = command_info
        if command_info.original_name != command_name:
            self._command_info_lookup[command_info.original_name] = command_info

    def add_subgroup_to_group(
        self,
        parent_group_name: str,
        subgroup_name: str,
        description: str,
        inner_class: type | None = None,
        **kwargs,
    ):
        """Add a subgroup to an existing group."""
        if parent_group_name not in self.tree:
            raise ValueError(f"Parent group '{parent_group_name}' not found")

        if self.tree[parent_group_name].get("type") != "group":
            raise ValueError(f"'{parent_group_name}' is not a group")

        self.tree[parent_group_name]["cmd_tree"][subgroup_name] = {
            "type": "group",
            "description": description,
            "cmd_tree": {},
            "inner_class": inner_class,
            **kwargs,
        }

    def add_command_to_subgroup(
        self,
        parent_group_name: str,
        subgroup_name: str,
        command_name: str,
        command_info: CommandInfo,
        **kwargs,
    ):
        """Add a command to a subgroup within a parent group."""
        if parent_group_name not in self.tree:
            raise ValueError(f"Parent group '{parent_group_name}' not found")

        parent_group = self.tree[parent_group_name]
        if parent_group.get("type") != "group":
            raise ValueError(f"'{parent_group_name}' is not a group")

        if subgroup_name not in parent_group["cmd_tree"]:
            raise ValueError(f"Subgroup '{subgroup_name}' not found in '{parent_group_name}'")

        subgroup = parent_group["cmd_tree"][subgroup_name]
        if subgroup.get("type") != "group":
            raise ValueError(f"'{subgroup_name}' is not a subgroup")

        subgroup["cmd_tree"][command_name] = {
            "type": "command",
            "function": command_info.function,
            "original_name": command_info.original_name,
            "group_name": f"{parent_group_name}.{subgroup_name}",
            "command_info": command_info,
            **kwargs,
        }

        # Update lookup table with full hierarchical path
        full_name = f"{parent_group_name} {subgroup_name} {command_name}"
        self._command_info_lookup[full_name] = command_info
        self._command_info_lookup[command_name] = command_info
        if command_info.original_name != command_name:
            self._command_info_lookup[command_info.original_name] = command_info

    def get_command(self, name: str) -> dict[str, Any] | None:
        """Get a command by name (supports both flat and hierarchical lookups)."""
        # Try direct lookup first
        if name in self.tree and self.tree[name].get("type") == "command":
            return self.tree[name]

        # Try hierarchical lookup (group command)
        for _, group_info in self.tree.items():
            if group_info.get("type") == "group" and "cmd_tree" in group_info:
                if name in group_info["cmd_tree"]:
                    return group_info["cmd_tree"][name]

        return None

    def get_group(self, name: str) -> dict[str, Any] | None:
        """Get a command group by name."""
        if name in self.tree and self.tree[name].get("type") == "group":
            return self.tree[name]
        return None

    def get_all_commands(self) -> list[CommandInfo]:
        """Get a flat list of all CommandInfo objects."""
        # Use a set to deduplicate CommandInfo objects (they should be unique by identity)
        seen = set()
        unique_commands = []
        for command_info in self._command_info_lookup.values():
            if id(command_info) not in seen:
                seen.add(id(command_info))
                unique_commands.append(command_info)
        return unique_commands

    def find_command_by_function(self, func_name: str) -> CommandInfo | None:
        """Find a command by function name."""
        # Try exact matches first
        if func_name in self._command_info_lookup:
            return self._command_info_lookup[func_name]

        # Try partial matches (for hierarchical cmd_tree)
        for name, command_info in self._command_info_lookup.items():
            if (
                command_info.original_name == func_name
                or command_info.name == func_name
                or name.endswith(f" {func_name}")
                or name.endswith(f"--{func_name}")
            ):
                return command_info

        return None

    def find_source_class(self, func_name: str) -> type | None:
        """Find the source class for a function name."""
        command_info = self.find_command_by_function(func_name)
        if command_info:
            return command_info.metadata.get("source_class")
        return None

    def to_dict(self) -> dict[str, dict[str, Any]]:
        """Return the raw hierarchical dictionary."""
        return self.tree

    def keys(self):
        """Return keys from the tree (for dict-like interface)."""
        return self.tree.keys()

    def __str__(self):
        return TextUtil.json_pretty(self.tree)

    def __getitem__(self, key: str):
        """Allow dict-like access to the tree."""
        return self.tree[key]

    def __contains__(self, key: str):
        """Check if key exists in the tree."""
        return key in self.tree

    def __len__(self):
        """Return the number of top-level items in the tree."""
        return len(self.tree)

    def __iter__(self):
        """Iterate over the tree keys."""
        return iter(self.tree)
