import inspect

from pyboot.component import Component
from pyboot.utils.log import Log


def ensure_component_subclass(cls, type_name, name):
    if not issubclass(cls, Component):
        new_cls  = type(cls.__name__, (Component, cls), {
            **cls.__dict__,
            "TYPE": type_name,
            "NAME": name
        })
        new_cls.__original_class__ = cls
    else:
        new_cls = cls
    for method_name, method in inspect.getmembers(cls, predicate=inspect.isfunction):
        if getattr(method, "__isabstractmethod__", False):
            Log.error(f"Component <{name}> contains abstract method <{method_name}>.", True)
        
    return cls


all_component_classes = {}
all_component_comments = {}

def register_component_type(component_type):
    all_component_classes[component_type] = {}
    all_component_comments[component_type] = {}

# --- Classes --- #

def component(component_type, component_name, comment=None):
    def decorator(cls):
        cls = ensure_component_subclass(cls, "COMPONENT", component_name)
        if component_type not in all_component_classes:
            register_component_type(component_type)
        all_component_classes[component_type][component_name] = cls
        all_component_comments[component_type][component_name] = comment
        return cls
    return decorator

def runner(runner_name, comment=None):
    def decorator(cls):
        if not hasattr(cls, 'run') or not callable(getattr(cls, 'run')):
            Log.error(f"runner <{cls.__name__}> must implement a 'run' method", True)
        cls = ensure_component_subclass(cls, "RUNNER", runner_name)
        if "runner" not in all_component_classes:
            register_component_type("runner")
        all_component_classes["runner"][runner_name] = cls
        all_component_comments["runner"][runner_name] = comment
        return cls
    return decorator

def factory(factory_name, comment=None):
    def decorator(cls):
        if not hasattr(cls, 'create') or not callable(getattr(cls, 'create')):
            Log.error(f"factory <{cls.__name__}> must implement a 'create' method", True)
        
        cls = ensure_component_subclass(cls, "FACTORY", factory_name)
        if "factory" not in all_component_classes:
            register_component_type("factory")
        all_component_classes["factory"][factory_name] = cls
        all_component_comments["factory"][factory_name] = comment
        return cls
    return decorator

def client(client_name, comment=None):
    def decorator(cls):
        cls = ensure_component_subclass(cls, "CLIENT", client_name)
        if "client" not in all_component_classes:
            register_component_type("client")
        all_component_classes["client"][client_name] = cls
        all_component_comments["client"][client_name] = comment
        return cls
    return decorator

def server(server_name, comment=None):
    def decorator(cls):
        cls = ensure_component_subclass(cls, "SERVER", server_name)
        if not hasattr(cls, 'host') or not hasattr(cls, 'port'):
            Log.error(f"server <{cls.__name__}> must implement 'host' and 'port' attributes", True)
        if not hasattr(cls, 'serve') or not callable(getattr(cls, 'serve')):
            Log.error(f"server <{cls.__name__}> must implement a 'serve' method", True)
        if "server" not in all_component_classes:
            register_component_type("server")
        all_component_classes["server"][server_name] = cls
        all_component_comments["server"][server_name] = comment
        return cls
    return decorator

# --- Utils --- #

def get_all_component_classes():
    return all_component_classes

def get_all_component_comments():
    return all_component_comments

def get_component_classes(component_type):
    return all_component_classes.get(component_type, None)