"""Positional parameter handling for CLI commands."""

import inspect
from typing import Any

from freyja.utils.text_util import TextUtil

from .argument_preprocessor import PositionalInfo


class PositionalHandler:
  """Handles positional parameter detection, validation, and conversion."""

  def __init__(self, positional_info: dict[str, PositionalInfo]):
    """Initialize positional handler with discovered positional parameters."""
    self.positional_info = positional_info

  def identify_positional_value(self, args: list[str], command_path: list[str]) -> tuple[str, str] | None:
    """Identify positional value in argument list."""
    if not command_path:
      return None

    # Get the command name
    command_name = self._get_command_name_from_path(command_path)
    if not command_name or command_name not in self.positional_info:
      return None

    pos_info = self.positional_info[command_name]

    # Look for positional value after command path
    search_start = len(command_path)

    for i in range(search_start, len(args)):
      arg = args[i]

      # Skip option flags and their values
      if arg.startswith('-'):
        # Skip this flag and potentially its value
        if self._flag_has_value(arg, args, i):
          i += 1  # Skip the value too
        continue

      # Found a positional argument
      return (pos_info.param_name, arg)

    return None

  def convert_positional_to_flag(self, param_name: str, param_value: str) -> list[str]:
    """Convert positional parameter to flag format."""
    flag_name = TextUtil.kebab_case(param_name)
    return [f"--{flag_name}", param_value]

  def validate_positional_value(self, param_name: str, param_value: str, param_type: type) -> tuple[bool, str | None]:
    """Validate positional parameter value matches expected type."""
    if param_type == str:
      return True, None

    if param_type == int:
      try:
        int(param_value)
        return True, None
      except ValueError:
        return False, f"Expected integer for {param_name}, got: {param_value}"

    if param_type == float:
      try:
        float(param_value)
        return True, None
      except ValueError:
        return False, f"Expected float for {param_name}, got: {param_value}"

    if param_type == bool:
      # Boolean positional parameters are tricky - typically they should be flags
      # But if specified, treat common boolean strings
      if param_value.lower() in ('true', 'false', '1', '0', 'yes', 'no'):
        return True, None
      return False, f"Expected boolean value (true/false, 1/0, yes/no) for {param_name}, got: {param_value}"

    # For other types, just accept the string - argparse will handle conversion
    return True, None

  def generate_positional_usage(self, command_name: str) -> str:
    """Generate usage text showing positional parameter."""
    if command_name not in self.positional_info:
      return ""

    pos_info = self.positional_info[command_name]
    param_display = pos_info.param_name.upper()

    if pos_info.is_required:
      return param_display
    else:
      return f"[{param_display}]"

  def has_positional_parameter(self, command_name: str) -> bool:
    """Check if a command has a positional parameter."""
    return command_name in self.positional_info

  def get_positional_info(self, command_name: str) -> PositionalInfo | None:
    """Get positional parameter info for a command."""
    return self.positional_info.get(command_name)

  def extract_and_convert_positional(self, args: list[str], command_path: list[str]) -> tuple[list[str], bool]:
    """Extract positional argument and convert to flag format."""
    if not command_path:
      return args, False

    command_name = self._get_command_name_from_path(command_path)
    if not command_name or command_name not in self.positional_info:
      return args, False

    pos_info = self.positional_info[command_name]
    modified_args = args.copy()
    conversion_made = False

    # Find and convert positional argument
    search_start = len(command_path)

    for i in range(search_start, len(modified_args)):
      arg = modified_args[i]

      # Skip option flags and their values
      if arg.startswith('-'):
        if self._flag_has_value(arg, modified_args, i):
          i += 1  # Skip the value too
        continue

      # Found positional argument - convert it
      flag_name = TextUtil.kebab_case(pos_info.param_name)

      # Validate before conversion
      is_valid, error_msg = self.validate_positional_value(
        pos_info.param_name,
        arg,
        pos_info.param_type
      )

      if not is_valid:
        raise ValueError(error_msg)

      # Replace positional with flag format
      modified_args[i] = f"--{flag_name}"
      modified_args.insert(i + 1, arg)
      conversion_made = True

      break

    return modified_args, conversion_made

  def _get_command_name_from_path(self, command_path: list[str]) -> str | None:
    """Get standardized command name from command path."""
    if not command_path:
      return None

    if len(command_path) == 1:
      return command_path[0]
    elif len(command_path) == 2:
      return f"{command_path[0]}--{command_path[1]}"

    return None

  def _flag_has_value(self, flag: str, args: list[str], flag_index: int) -> bool:
    """Check if a flag expects a value (not a store_true flag)."""
    # Simple heuristic: if next arg exists and doesn't start with '-', it's probably a value
    if flag_index + 1 < len(args):
      next_arg = args[flag_index + 1]
      if not next_arg.startswith('-'):
        return True

    # Check for known store_true flags (flags that don't take values)
    store_true_flags = {
      '--help', '-h', '--verbose', '-v', '--no-color', '-n',
      '--dry-run', '--force', '--debug', '--quiet', '-q',
      '--excited', '--backup', '--compress', '--detailed',
      '--parallel', '--preserve-original', '--include-metadata'
    }

    return flag not in store_true_flags

  @staticmethod
  def discover_from_function(function: Any) -> PositionalInfo | None:
    """Discover positional parameter from function signature."""
    if not function:
      return None

    try:
      sig = inspect.signature(function)
      for param_name, param in sig.parameters.items():
        if param_name == 'self':
          continue

        # First parameter without default becomes positional
        if param.default == param.empty:
          return PositionalInfo(
            param_name=param_name,
            param_type=param.annotation if param.annotation != param.empty else str,
            is_required=True,
            help_text=f"{param_name} parameter"
          )
    except (ValueError, TypeError):
      # Handle cases where signature inspection fails
      pass

    return None

  @staticmethod
  def discover_from_command_tree(command_tree) -> dict[str, PositionalInfo]:
    """Discover all positional parameters from a command tree."""
    positional_params = {}

    # Discover from flat commands
    if hasattr(command_tree, 'flat_commands') and command_tree.flat_commands:
      for cmd_name, cmd_info in command_tree.flat_commands.items():
        if 'function' in cmd_info:
          pos_info = PositionalHandler.discover_from_function(cmd_info['function'])
          if pos_info:
            positional_params[cmd_name] = pos_info

    # Discover from grouped commands
    if hasattr(command_tree, 'groups') and command_tree.groups:
      for group_name, group_info in command_tree.groups.items():
        if 'commands' in group_info:
          for cmd_name, cmd_info in group_info['commands'].items():
            full_cmd_name = f"{group_name}--{cmd_name}"
            if 'function' in cmd_info:
              pos_info = PositionalHandler.discover_from_function(cmd_info['function'])
              if pos_info:
                positional_params[full_cmd_name] = pos_info

    return positional_params
