import functools
import logging
import traceback
from abc import ABC, abstractmethod
from datetime import datetime
from multiprocessing import Process
from typing import Any, Callable, Dict, List, Optional, Set, Type

from keven_core.kafka.commands.command import Command, CommandPhase, CommandNames
from keven_core.kafka.commands.dispatcher import (
    keven_dispatch_command,
    clear_command_publishers,
)
from keven_core.kafka.events.event import Event, keven_log_event_now
from keven_core.kafka.events.logger import keven_clear_event_publishers
from keven_core.kafka.abstracts.delayed import DelayedCommandsAndEvents
from keven_core.utils.threading.thread_mgr import ThreadManager
from keven_core.kafka.abstracts.metaclass.base_handler_meta import BaseHandlerMeta

class CommandHandler(ABC, metaclass=BaseHandlerMeta):
    """
    Abstract base class for handling commands with automatic registration via BaseHandlerMeta.

    A subclass must define command_name = <some CommandNames> and must ensure event_name = None.
    If both or neither are defined, a TypeError will be raised.

    Features:
      - Function registration (register_function).
      - Pre-/post-handler hooks.
      - Delayed commands and events if delay_commands_and_events is True.
      - Automatic generation of start, error, and complete events for each command.
      - create_handler_server_process method to run a command receiver in a separate process.

    Usage Example:
        class MyCommandHandler(CommandHandler):
            command_name = CommandNames.MY_COMMAND  # Required
            event_name = None  # Must remain None

            def handle_command(self, command: Command) -> None:
                print(f"Processing command: {command}")
    """
    command_name: Optional[CommandNames] = None
    event_name = None  # Must be None to avoid conflict in BaseHandlerMeta
    auto_register: bool = True
    delay_commands_and_events: bool = True
    capture_exception_handler: Optional[Callable[[Exception], None]] = None

    _func_registry: Dict[CommandNames, List[Callable[[Any], None]]] = {}
    _func_pre_handler_hooks: List[Callable[[], None]] = []
    _func_post_handler_hooks: List[Callable[[], None]] = []

    def __init__(self, command: Command) -> None:
        self.command: Command = command
        self.additional_commands: List[Command] = []
        self.error_message: str = ""

    @classmethod
    def register_function(cls, command_name: CommandNames, func: Callable[[Any], None]) -> None:
        """
        Registers a function to be invoked for a given command_name.
        """
        if command_name not in cls._func_registry:
            cls._func_registry[command_name] = []
        cls._func_registry[command_name].append(func)

    @classmethod
    def register_pre_handler_hook(cls, func: Callable[[], None]) -> None:
        """
        Registers a pre-handler hook executed before handling a command.
        """
        cls._func_pre_handler_hooks.append(func)

    @classmethod
    def register_post_handler_hook(cls, func: Callable[[], None]) -> None:
        """
        Registers a post-handler hook executed after handling a command.
        """
        cls._func_post_handler_hooks.append(func)

    @classmethod
    def handle_all_commands(cls, command: Command) -> None:
        """
        Processes a command using all registered CommandHandlers across any command_name.
        """
        from keven_core.kafka.abstracts.metaclass.base_handler_meta import BaseHandlerMeta
        registry = BaseHandlerMeta.get_command_registry()
        for cmd_name, handler_set in registry.items():
            for handler_class in handler_set:
                cls.execution_handler_according_to_protocol(command, handler_class)

    @classmethod
    def handle_commands(cls, command: Command) -> None:
        """
        Processes a command using all handlers and functions associated with its command_name.
        """
        from keven_core.kafka.abstracts.metaclass.base_handler_meta import BaseHandlerMeta
        registry = BaseHandlerMeta.get_command_registry()

        if command.command_name in registry:
            for handler_class in registry[command.command_name]:
                cls.execution_handler_according_to_protocol(command, handler_class)

        if command.command_name in cls._func_registry:
            for func in cls._func_registry[command.command_name]:
                cls.execute_pre_handler_hooks()
                func(command)
                cls.execute_post_handler_hooks()

    @classmethod
    def execution_handler_according_to_protocol(cls, command: Command, handler_class: Type["CommandHandler"]) -> None:
        """
        Executes the specified handler, optionally within DelayedCommandsAndEvents,
        then dispatches any additional commands generated by the handler.
        """
        if cls.delay_commands_and_events:
            with DelayedCommandsAndEvents():
                handler = cls.execute_handler(command, handler_class)
        else:
            handler = cls.execute_handler(command, handler_class)

        for add_cmd in handler.additional_commands:
            keven_dispatch_command(add_cmd)

    @classmethod
    def execute_handler(cls, command: Command, handler_class: Type["CommandHandler"]) -> "CommandHandler":
        """
        Runs pre-handler hooks, instantiates the handler, logs start/error/complete events,
        and runs post-handler hooks.
        """
        cls.execute_pre_handler_hooks()
        handler = handler_class(command)
        started_event = handler.start_event(get_old_event=False)
        started_time = datetime.now()
        if started_event is not None:
            started_time = started_event.created
            keven_log_event_now(started_event)
        try:
            handler.handle_command(command)
        except Exception as e:
            if cls.capture_exception_handler:
                cls.capture_exception_handler(e)
            logging.error(traceback.format_exc())
            cls.execute_post_handler_hooks()
            errored_event = handler.error_event(get_old_event=False)
            if errored_event is not None:
                errored_event.error_message = str(e)
                errored_time = datetime.now()
                errored_event.compute_time = (errored_time - started_time).microseconds
                keven_log_event_now(errored_event)
            return handler
        cls.execute_post_handler_hooks()
        completed_event = handler.complete_event(get_old_event=False)
        if completed_event is not None:
            completed_time = datetime.now()
            completed_event.compute_time = (completed_time - started_time).microseconds
            keven_log_event_now(completed_event)
        return handler

    @classmethod
    def execute_pre_handler_hooks(cls) -> None:
        """
        Executes all registered pre-handler hooks.
        """
        for hook in cls._func_pre_handler_hooks:
            hook()

    @classmethod
    def execute_post_handler_hooks(cls) -> None:
        """
        Executes all registered post-handler hooks.
        """
        for hook in cls._func_post_handler_hooks:
            logging.debug(f"Executing post-handler hook: {hook.__name__}")
            hook()

    @classmethod
    def commands_with_handlers(cls) -> Set[str]:
        """
        Returns a set of command names (as lowercase strings) for which handlers are registered.
        """
        from keven_core.kafka.abstracts.metaclass.base_handler_meta import BaseHandlerMeta
        registry = BaseHandlerMeta.get_command_registry()
        return {cmd_name.value.lower() for cmd_name in registry.keys()}

    @abstractmethod
    def handle_command(self, command: Command) -> None:
        """
        Must be overridden by subclasses to define how the command is processed.
        """
        pass

    def start_event(self, include_source: bool = True, get_old_event: bool = True) -> Optional[Event]:
        event = Event.from_name(self.command.get_event_name(CommandPhase.STARTED, old_name=get_old_event))
        if event and include_source:
            event.source_command = self.command
        return event

    def complete_event(self, include_source: bool = True, get_old_event: bool = True) -> Optional[Event]:
        event = Event.from_name(self.command.get_event_name(CommandPhase.COMPLETED, old_name=get_old_event))
        if event and include_source:
            event.source_command = self.command
        return event

    def error_event(self, msg: str = "", include_source: bool = True, get_old_event: bool = True) -> Optional[Event]:
        error_message = msg or self.error_message or ""
        logging.error(error_message)
        event = Event.from_name(self.command.get_event_name(CommandPhase.ERRORED, old_name=get_old_event))
        if event and include_source:
            event.source_command = self.command
        if event:
            event.error_message = error_message
            event.traceback = traceback.format_exc()
        return event

    @classmethod
    def get_handler_count(cls, command_name: CommandNames) -> int:
        from keven_core.kafka.abstracts.metaclass.base_handler_meta import BaseHandlerMeta
        registry = BaseHandlerMeta.get_command_registry()
        if command_name in registry:
            return len(registry[command_name])
        return 0

    @classmethod
    def create_handler_server_process(cls, name: str, io_server_factory: Optional[Callable] = None) -> Process:
        """
        Spawns a new process that runs a command receiver server.

        Clears command and event publishers before starting.
        """
        def process_method(a_name: str, p_io_server: Optional[Any]) -> None:
            from keven_core.kafka.commands.command_server import start_command_receiver_server
            clear_command_publishers()
            keven_clear_event_publishers()

            start_command_receiver_server(
                a_name,
                auto_reprocess_errors=True,
                max_poll_interval=5 * 60,
                io_server=p_io_server,
            )

        return Process(target=process_method, args=(name, None))

# Optional function-based command handlers:
def command_handler(command_name: CommandNames, delay_commands_and_events: bool = True,
                    exception_handler: Optional[Callable[[Exception], None]] = None) -> Callable:
    """
    Decorator to convert a function into a command handler for the specified command_name.
    """
    def command_handler_decorator(func: Callable) -> Callable:
        from keven_core.kafka.commands.command import Command
        cls = Command.create_handler(
            command_name,
            func,
            delay_commands_and_events=delay_commands_and_events,
        )
        cls.capture_exception_handler = exception_handler
        print(cls)

        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return cls.func(*args, **kwargs)

        return wrapper

    return command_handler_decorator


class LongRunningCommandHandler(CommandHandler):
    """
    Specialized CommandHandler for long-running tasks using a ThreadManager.

    Must assign a ThreadManager to the thread_manager attribute.
    """
    thread_manager: Optional[ThreadManager] = None

    def long_running_handle_command(self, command: Command) -> None:
        raise NotImplementedError("Please implement me")

    def handle_command(self, command: Command) -> None:
        if not self.thread_manager:
            raise ValueError("You must set a thread_manager")
        self.thread_manager.queue_object(self)


def long_running_command_handler(command_handler: LongRunningCommandHandler) -> None:
    """
    Helper for executing a long-running command handler (once dequeued by ThreadManager).
    """
    command_handler.long_running_handle_command(command_handler.command)
