import logging
import uuid
from typing import Optional

from fasthtml.fastapp import fast_app

from myfasthtml.core.constants import Routes, ROUTE_ROOT
from myfasthtml.core.utils import mount_if_not_exists

commands_app, commands_rt = fast_app()
logger = logging.getLogger("Commands")


class BaseCommand:
  """
  Represents the base command class for defining executable actions.

  This class serves as a foundation for commands that can be registered,
  executed, and utilized within a system. Each command has a unique identifier,
  a name, and a description. Commands should override the `execute` method
  to provide specific functionality.

  :ivar id: A unique identifier for the command.
  :type id: uuid.UUID
  :ivar name: The name of the command.
  :type name: str
  :ivar description: A brief description of the command's functionality.
  :type description: str
  """
  
  def __init__(self, name, description):
    self.id = uuid.uuid4()
    self.name = name
    self.description = description
    
    # register the command
    CommandsManager.register(self)
  
  def get_htmx_params(self):
    return {
        "hx-post": f"{ROUTE_ROOT}{Routes.Commands}",
        "hx-vals": f'{{"c_id": "{self.id}"}}',
    }
  
  def execute(self):
    raise NotImplementedError


class Command(BaseCommand):
  """
  Represents a command that encapsulates a callable action with parameters.

  This class is designed to hold a defined action (callback) alongside its arguments
  and keyword arguments.
  
  :ivar name: The name of the command.
  :type name: str
  :ivar description: A brief description of the command.
  :type description: str
  :ivar callback: The function or callable to be executed.
  :type callback: Callable
  :ivar args: Positional arguments to be passed to the callback.
  :type args: tuple
  :ivar kwargs: Keyword arguments to be passed to the callback.
  :type kwargs: dict
  """
  
  def __init__(self, name, description, callback, *args, **kwargs):
    super().__init__(name, description)
    self.callback = callback
    self.args = args
    self.kwargs = kwargs
  
  def execute(self):
    return self.callback(*self.args, **self.kwargs)
  
  def __str__(self):
    return f"Command({self.name})"


class CommandsManager:
  commands = {}
  
  @staticmethod
  def register(command: BaseCommand):
    CommandsManager.commands[str(command.id)] = command
  
  @staticmethod
  def get_command(command_id: str) -> Optional[BaseCommand]:
    return CommandsManager.commands.get(command_id)
  
  @staticmethod
  def reset():
    return CommandsManager.commands.clear()


@commands_rt(Routes.Commands)
def post(session: str, c_id: str):
  """
  Default routes for all commands.
  :param session:
  :param c_id:
  :return:
  """
  logger.debug(f"Entering {Routes.Commands} with {session=}, {c_id=}")
  command = CommandsManager.get_command(c_id)
  if command:
    return command.execute()
  
  raise ValueError(f"Command with ID '{c_id}' not found.")


def mount_commands(app):
  """
  Mounts the commands_app to the given application instance if the route does not already exist.

  :param app: The application instance to which the commands_app will be mounted.
  :type app: Any
  :return: Returns the result of the mount operation performed by mount_if_not_exists.
  :rtype: Any
  """
  return mount_if_not_exists(app, ROUTE_ROOT, commands_app)
