"""
Command Parser - Parse and load slash commands

Slash commands follow the syntax: /command-name args
Commands are loaded from:
1. .groknroll/command/<name>.md (project-level)
2. ~/.config/groknroll/command/<name>.md (global)
3. groknroll.json config (inline commands)

Search order: project → global → config (first match wins)
"""

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


@dataclass
class Command:
    """
    Represents a parsed command

    Attributes:
        name: Command name (without leading /)
        args: Arguments passed to the command
        template: Command template content (from .md file or config)
        description: Human-readable description
        path: Path to command file (if loaded from file)
        metadata: Additional metadata
    """

    name: str
    args: str = ""
    template: str = ""
    description: str = ""
    path: Optional[Path] = None
    metadata: dict = field(default_factory=dict)

    def __str__(self) -> str:
        return f"Command(/{self.name})"

    def __repr__(self) -> str:
        return f"Command(name='{self.name}', args='{self.args}', description='{self.description}')"


class CommandParser:
    """
    Parse and load slash commands

    Commands are loaded from:
    1. .groknroll/command/<name>.md (project-level)
    2. ~/.config/groknroll/command/<name>.md (global)
    3. groknroll.json config (inline commands)

    Example:
        parser = CommandParser(project_path=Path.cwd())

        # Parse user input
        cmd = parser.parse("/test-unit auth")
        if cmd:
            print(f"Running: {cmd.name} with args: {cmd.args}")

        # List all available commands
        for cmd in parser.list_commands():
            print(f"/{cmd.name}: {cmd.description}")

        # Get autocomplete suggestions
        suggestions = parser.autocomplete("/te")
    """

    COMMAND_FILENAME = "COMMAND.md"
    PROJECT_COMMAND_DIR = ".groknroll/command"
    GLOBAL_COMMAND_DIR = "~/.config/groknroll/command"
    COMMAND_PATTERN = re.compile(r"^/([a-z][a-z0-9-]*)(?:\s+(.*))?$", re.IGNORECASE)

    def __init__(self, project_path: Optional[Path] = None):
        """
        Initialize CommandParser

        Args:
            project_path: Project root directory (defaults to cwd)
        """
        self.project_path = project_path or Path.cwd()
        self._cache: dict[str, Command] = {}
        self._discovered = False
        self._config_commands: dict[str, dict] = {}
        self._load_config_commands()

    @property
    def project_command_dir(self) -> Path:
        """Get project-level command directory"""
        return self.project_path / self.PROJECT_COMMAND_DIR

    @property
    def global_command_dir(self) -> Path:
        """Get global command directory"""
        return Path(self.GLOBAL_COMMAND_DIR).expanduser()

    @property
    def config_path(self) -> Path:
        """Get config file path"""
        return self.project_path / "groknroll.json"

    def parse(self, input_text: str) -> Optional[Command]:
        """
        Parse user input for a slash command

        Args:
            input_text: User input string (e.g., "/test-unit auth module")

        Returns:
            Command if input is a valid slash command, None otherwise

        Example:
            cmd = parser.parse("/test-unit auth")
            if cmd:
                print(f"Command: {cmd.name}, Args: {cmd.args}")
        """
        input_text = input_text.strip()

        # Must start with /
        if not input_text.startswith("/"):
            return None

        # Match command pattern
        match = self.COMMAND_PATTERN.match(input_text)
        if not match:
            return None

        name = match.group(1).lower()
        args = (match.group(2) or "").strip()

        # Load the command template
        cmd = self._load_command(name)
        if cmd:
            # Update with parsed args
            cmd.args = args
            return cmd

        # Command not found - return basic command without template
        return Command(name=name, args=args)

    def _load_command(self, name: str) -> Optional[Command]:
        """
        Load a command by name

        Args:
            name: Command name (without /)

        Returns:
            Command if found, None otherwise
        """
        # Check cache first
        if name in self._cache:
            cmd = self._cache[name]
            # Return a copy with empty args (args are set by parse())
            return Command(
                name=cmd.name,
                args="",
                template=cmd.template,
                description=cmd.description,
                path=cmd.path,
                metadata=cmd.metadata.copy(),
            )

        # Search project directory first, then global
        for cmd_dir in [self.project_command_dir, self.global_command_dir]:
            cmd_path = cmd_dir / name / self.COMMAND_FILENAME

            if cmd_path.exists():
                cmd = self._load_command_file(name, cmd_path)
                if cmd:
                    self._cache[name] = cmd
                    return Command(
                        name=cmd.name,
                        args="",
                        template=cmd.template,
                        description=cmd.description,
                        path=cmd.path,
                        metadata=cmd.metadata.copy(),
                    )

        # Check config commands
        if name in self._config_commands:
            cmd = self._load_config_command(name)
            if cmd:
                self._cache[name] = cmd
                return Command(
                    name=cmd.name,
                    args="",
                    template=cmd.template,
                    description=cmd.description,
                    path=cmd.path,
                    metadata=cmd.metadata.copy(),
                )

        return None

    def _load_command_file(self, name: str, path: Path) -> Optional[Command]:
        """
        Load a command from a COMMAND.md file

        Args:
            name: Command name
            path: Path to COMMAND.md file

        Returns:
            Command instance or None if loading fails
        """
        try:
            content = path.read_text(encoding="utf-8")
            description, metadata = self._parse_frontmatter(content)

            return Command(
                name=name,
                template=content,
                description=description,
                path=path,
                metadata=metadata,
            )
        except Exception:
            return None

    def _load_config_command(self, name: str) -> Optional[Command]:
        """
        Load a command from groknroll.json config

        Args:
            name: Command name

        Returns:
            Command instance or None if not found
        """
        config = self._config_commands.get(name)
        if not config:
            return None

        template = config.get("template", "")
        description = config.get("description", "")

        return Command(
            name=name,
            template=template,
            description=description,
            metadata={"source": "config"},
        )

    def _load_config_commands(self) -> None:
        """Load commands from groknroll.json"""
        if not self.config_path.exists():
            return

        try:
            with open(self.config_path) as f:
                config = json.load(f)

            commands = config.get("command", {})
            if isinstance(commands, dict):
                self._config_commands = commands
        except (json.JSONDecodeError, IOError):
            pass

    def _parse_frontmatter(self, content: str) -> tuple[str, dict]:
        """
        Parse YAML frontmatter from command content

        Args:
            content: Full COMMAND.md content

        Returns:
            Tuple of (description, metadata dict)
        """
        description = ""
        metadata: dict = {}

        # Check for YAML frontmatter (between --- markers)
        if content.startswith("---"):
            lines = content.split("\n")
            end_index = -1

            # Find closing ---
            for i, line in enumerate(lines[1:], start=1):
                if line.strip() == "---":
                    end_index = i
                    break

            if end_index > 0:
                # Extract frontmatter lines
                frontmatter_lines = lines[1:end_index]

                # Simple YAML parsing (key: value pairs)
                for line in frontmatter_lines:
                    if ":" in line:
                        key, _, value = line.partition(":")
                        key = key.strip()
                        value = value.strip().strip("\"'")

                        if key == "description":
                            description = value
                        elif key == "name":
                            metadata["name"] = value
                        else:
                            metadata[key] = value

        # If no description in frontmatter, use first heading or line
        if not description:
            lines = content.split("\n")
            in_frontmatter = content.startswith("---")
            frontmatter_ended = not in_frontmatter

            for line in lines:
                if in_frontmatter:
                    if line.strip() == "---" and frontmatter_ended:
                        in_frontmatter = False
                    elif line.strip() == "---":
                        frontmatter_ended = True
                    continue

                stripped = line.strip()
                if stripped and not stripped.startswith("#"):
                    description = stripped[:100]
                    break
                elif stripped.startswith("#"):
                    description = stripped.lstrip("#").strip()[:100]
                    break

        return description or "No description", metadata

    def list_commands(self) -> list[Command]:
        """
        List all available commands

        Discovers and loads all commands from project, global, and config.
        Project commands take precedence over global commands with same name.

        Returns:
            List of all available Command instances
        """
        if not self._discovered:
            self._discover_all()
            self._discovered = True

        return list(self._cache.values())

    def get_command_names(self) -> list[str]:
        """
        Get list of all available command names

        Returns:
            Sorted list of command names (without /)
        """
        if not self._discovered:
            self._discover_all()
            self._discovered = True

        return sorted(self._cache.keys())

    def _discover_all(self) -> None:
        """Discover all commands in project, global directories, and config"""
        # Config first (lowest priority - will be overwritten)
        for name, config in self._config_commands.items():
            if name not in self._cache:
                cmd = self._load_config_command(name)
                if cmd:
                    self._cache[name] = cmd

        # Global second (will overwrite config)
        if self.global_command_dir.exists():
            for cmd_subdir in self.global_command_dir.iterdir():
                if not cmd_subdir.is_dir():
                    continue

                cmd_path = cmd_subdir / self.COMMAND_FILENAME
                if cmd_path.exists():
                    cmd = self._load_command_file(cmd_subdir.name, cmd_path)
                    if cmd:
                        self._cache[cmd.name] = cmd

        # Project last (highest priority - overwrites all)
        if self.project_command_dir.exists():
            for cmd_subdir in self.project_command_dir.iterdir():
                if not cmd_subdir.is_dir():
                    continue

                cmd_path = cmd_subdir / self.COMMAND_FILENAME
                if cmd_path.exists():
                    cmd = self._load_command_file(cmd_subdir.name, cmd_path)
                    if cmd:
                        self._cache[cmd.name] = cmd

    def autocomplete(self, partial: str) -> list[str]:
        """
        Get autocomplete suggestions for partial command input

        Args:
            partial: Partial command input (e.g., "/te" or "te")

        Returns:
            List of matching command names with / prefix

        Example:
            suggestions = parser.autocomplete("/te")
            # Returns: ["/test", "/test-unit", "/teardown"]
        """
        # Ensure commands are discovered
        if not self._discovered:
            self._discover_all()
            self._discovered = True

        # Remove leading / if present
        search = partial.lstrip("/").lower()

        # Find matching commands
        matches = []
        for name in self._cache.keys():
            if name.startswith(search):
                matches.append(f"/{name}")

        return sorted(matches)

    def reload(self) -> None:
        """Clear cache and reload all commands"""
        self._cache.clear()
        self._discovered = False
        self._config_commands.clear()
        self._load_config_commands()

    def __len__(self) -> int:
        """Return number of available commands"""
        if not self._discovered:
            self._discover_all()
            self._discovered = True
        return len(self._cache)

    def __contains__(self, name: str) -> bool:
        """Check if command exists (supports 'in' operator)"""
        return self._load_command(name) is not None

    def __str__(self) -> str:
        return f"CommandParser(project={self.project_path})"

    def __repr__(self) -> str:
        return f"CommandParser(project_path='{self.project_path}', cached={len(self._cache)})"


# Convenience function for quick command parsing
def parse_command(
    input_text: str,
    project_path: Optional[Path] = None,
) -> Optional[Command]:
    """
    Parse a slash command from input (convenience function)

    Args:
        input_text: User input string
        project_path: Project root directory

    Returns:
        Command if input is a valid slash command, None otherwise
    """
    parser = CommandParser(project_path=project_path)
    return parser.parse(input_text)
