"""
ALASK v2.0 - Task Decorators
- rmi_task: Task 클래스 등록
- rmi_run: 메인 실행 메서드 표시
- rmi_signal: Signal 핸들러 등록
- TaskValidator: Task 클래스 검증
"""

from __future__ import annotations
from typing import Callable, Dict, List, Optional, Tuple

_task_registry: Dict[str, type] = {}


class rmi_task:
    """Task 클래스 등록 데코레이터

    @rmi_task(name="worker", mode="process")
    class WorkerTask:
        def __init__(self, runtime): ...
        def run(self, runtime): ...
    """
    __slots__ = ('name', 'mode', 'restart', 'restart_delay')

    def __init__(self, name: str, mode: str, restart: bool = True, restart_delay: float = 3.0):
        self.name = name
        self.mode = mode
        self.restart = restart
        self.restart_delay = restart_delay

    def __call__(self, cls):
        _task_registry[self.name] = cls
        cls._tc_name = self.name
        cls._tc_mode = self.mode
        cls._tc_restart = self.restart
        cls._tc_delay = self.restart_delay
        return cls

    @staticmethod
    def get(name: str) -> Optional[type]:
        return _task_registry.get(name)

    @staticmethod
    def attr(name: str, key: str, default=None):
        cls = _task_registry.get(name)
        return getattr(cls, key, default) if cls else default

    @staticmethod
    def all() -> Dict[str, type]:
        return _task_registry.copy()


class rmi_run:
    """메인 실행 메서드 표시 (@rmi_run 또는 @rmi_run() 둘 다 지원)"""
    __slots__ = ('_func',)

    def __new__(cls, func=None):
        if func is not None:
            func._rmi_run = True
            return func
        instance = object.__new__(cls)
        instance._func = None
        return instance

    def __call__(self, func):
        func._rmi_run = True
        return func


class rmi_signal:
    """Signal 핸들러 메서드 표시

    @rmi_signal("camera.connected")
    def _on_connected(self, signal): ...
    """
    __slots__ = ('signal_name',)

    def __init__(self, signal_name: str):
        self.signal_name = signal_name

    def __call__(self, func):
        func._signal_handler = self.signal_name
        return func


def has_run_flag(method) -> bool:
    """@rmi_run 데코레이터 플래그 확인"""
    fn = getattr(method, '__func__', method)
    return getattr(fn, '_rmi_run', False) or getattr(fn, '_rmi_loop', False)


def get_signal_handlers(obj) -> List[Tuple[str, Callable]]:
    """@rmi_signal 데코레이터가 붙은 메서드 목록 반환"""
    handlers = []
    for name in dir(obj):
        if name.startswith('_'):
            method = getattr(obj, name, None)
            if callable(method) and hasattr(method, '_signal_handler'):
                handlers.append((method._signal_handler, method))
    return handlers


class TaskValidator:
    """Task 클래스 검증기 - __init__(self, runtime), run(self, runtime) 시그니처 검증"""

    REQUIRED_ATTRS = ['_tc_name', '_tc_mode', '_tc_restart', '_tc_delay']
    ATTR_DISPLAY = {'_tc_name': 'name', '_tc_mode': 'mode', '_tc_restart': 'restart', '_tc_delay': 'restart_delay'}
    VALID_MODES = ('thread', 'process')

    def __init__(self):
        self._tasks = []
        self._invalid_tasks = []
        self._missing_attrs = []
        self._invalid_modes = []
        self._missing_run = []
        self._invalid_init = []
        self._invalid_run_sig = []

    def add(self, task_id: str, task_class_name: str):
        self._tasks.append((task_id, task_class_name))

    def validate(self) -> bool:
        import inspect
        self._invalid_tasks.clear()
        self._missing_attrs.clear()
        self._invalid_modes.clear()
        self._missing_run.clear()
        self._invalid_init.clear()
        self._invalid_run_sig.clear()

        for tid, tcn in self._tasks:
            cls = rmi_task.get(tcn)
            if cls is None:
                self._invalid_tasks.append((tid, tcn))
                continue

            missing = [self.ATTR_DISPLAY[a] for a in self.REQUIRED_ATTRS if not hasattr(cls, a)]
            if missing:
                self._missing_attrs.append((tid, tcn, missing))

            mode = getattr(cls, '_tc_mode', None)
            if mode and mode not in self.VALID_MODES:
                self._invalid_modes.append((tid, tcn, mode))

            if not self._has_run_method(cls):
                self._missing_run.append((tid, tcn))

            if hasattr(cls, '__init__'):
                params = list(inspect.signature(cls.__init__).parameters.keys())
                if len(params) != 2 or params[0] != 'self':
                    src, line = self._get_source_location(cls, cls.__init__)
                    self._invalid_init.append((tid, tcn, params, src, line))

            run_method, run_name = self._find_run_method(cls)
            if run_method:
                params = list(inspect.signature(run_method).parameters.keys())
                if len(params) != 2 or params[0] != 'self':
                    src, line = self._get_source_location(cls, run_method)
                    self._invalid_run_sig.append((tid, tcn, run_name, params, src, line))

        return not (self._invalid_tasks or self._missing_attrs or self._invalid_modes or
                   self._missing_run or self._invalid_init or self._invalid_run_sig)

    def _has_run_method(self, cls: type) -> bool:
        has_run = hasattr(cls, 'run') and callable(getattr(cls, 'run', None))
        has_loop = hasattr(cls, 'rmi_loop') and callable(getattr(cls, 'rmi_loop', None))
        has_deco = any(
            getattr(getattr(cls, m, None), '_rmi_run', False) or getattr(getattr(cls, m, None), '_rmi_loop', False)
            for m in dir(cls) if not m.startswith('__'))
        return has_run or has_loop or has_deco

    def _find_run_method(self, cls: type) -> tuple:
        for m in dir(cls):
            if m.startswith('__'): continue
            method = getattr(cls, m, None)
            if callable(method) and (getattr(method, '_rmi_run', False) or getattr(method, '_rmi_loop', False)):
                return (method, m)
        if hasattr(cls, 'run') and callable(getattr(cls, 'run', None)):
            return (getattr(cls, 'run'), 'run')
        if hasattr(cls, 'rmi_loop') and callable(getattr(cls, 'rmi_loop', None)):
            return (getattr(cls, 'rmi_loop'), 'rmi_loop')
        return (None, None)

    def _get_source_location(self, cls: type, method=None) -> tuple:
        import inspect, os
        src, line = None, 0
        try: src = inspect.getfile(cls)
        except: pass
        if src is None:
            try: src = inspect.getsourcefile(cls)
            except: pass
        if src: src = os.path.abspath(src)
        try:
            fn = getattr(method, '__func__', method) if method else cls
            _, line = inspect.getsourcelines(fn)
        except: pass
        return (src, line)

    def print_errors(self):
        print("\n" + "=" * 60)
        print("[Validator] ERROR: rmi_task validation failed!")
        print("=" * 60)

        if self._invalid_tasks:
            print("\nUnregistered rmi_task:")
            for tid, tcn in self._invalid_tasks:
                print(f"  - Task '{tid}': rmi_task '{tcn}' not found")

        if self._missing_attrs:
            print("\nMissing rmi_task attributes:")
            for tid, tcn, attrs in self._missing_attrs:
                print(f"  - Task '{tid}': rmi_task '{tcn}' missing: {', '.join(attrs)}")

        if self._invalid_modes:
            print("\nInvalid mode value:")
            for tid, tcn, mode in self._invalid_modes:
                print(f"  - Task '{tid}': rmi_task '{tcn}' mode='{mode}' (must be thread/process)")

        if self._missing_run:
            print("\nMissing run() method:")
            for tid, tcn in self._missing_run:
                print(f"  - Task '{tid}': rmi_task '{tcn}' must define run(self, runtime)")

        C, U, R = "\033[36m", "\033[4m", "\033[0m"  # Cyan, Underline, Reset

        if self._invalid_init:
            print("\nInvalid __init__ signature:")
            for tid, tcn, params, src, line in self._invalid_init:
                print(f"  - Task '{tid}': rmi_task '{tcn}' __init__ must be (self, runtime), got ({', '.join(params)})")
                if src: print(f"    {C}{U}{src}:{line}{R}")

        if self._invalid_run_sig:
            print("\nInvalid @rmi_run method signature:")
            for tid, tcn, name, params, src, line in self._invalid_run_sig:
                print(f"  - Task '{tid}': rmi_task '{tcn}' {name}() must be (self, runtime), got ({', '.join(params)})")
                if src: print(f"    {C}{U}{src}:{line}{R}")

        print("\nHint: def __init__(self, runtime) and def run(self, runtime)")
        print("=" * 60 + "\n")

    @property
    def has_errors(self) -> bool:
        return bool(self._invalid_tasks or self._missing_attrs or self._invalid_modes or
                   self._missing_run or self._invalid_init or self._invalid_run_sig)
