from abc import ABC, abstractmethod
from typing import Any, Awaitable, Callable, Mapping, TYPE_CHECKING, cast

from greyhorse.result import Result
from .deps import DepsOperator, DepsProvider
from .operator import OperatorFactoryFn, OperatorFactoryRegistry, OperatorKey
from .providers import ProviderFactoryFn, ProviderKey
from ..errors import NoOpFoundForPattern, OpPolicyViolation
from ..utils.registry import DictRegistry, KeyMapping
from ...i18n import tr
from ...logging import logger

if TYPE_CHECKING:
    from ..schemas.controller import OperatorMappingPolicy

type ControllerKey = type[Controller]
type ControllerFactoryFn = Callable[[...], Result[Controller]]
type ControllerFactoryMapping = Mapping[ControllerKey, ControllerFactoryFn]


class Controller(ABC):
    def __init__(
        self, name: str,
        deps_provider: DepsProvider | None = None,
        deps_operator: DepsOperator | None = None,
    ):
        self._name = name
        self._deps_provider = deps_provider
        self._deps_operator = deps_operator
        self._op_factories = DictRegistry[OperatorKey, OperatorFactoryFn]()

    @property
    def name(self) -> str:
        return self._name

    @property
    @abstractmethod
    def active(self) -> bool:
        ...

    def set_deps_provider(self, instance: DepsProvider):
        self._deps_provider = instance

    def reset_deps_provider(self):
        self._deps_provider = None

    def set_deps_operator(self, instance: DepsOperator):
        self._deps_operator = instance

    def reset_deps_operator(self):
        self._deps_operator = None

    def get_operator_factory(self, key: OperatorKey, name: str | None = None) -> OperatorFactoryFn | None:
        if not self._deps_provider:
            return None
        return self._deps_provider.get_operator_factory(key, name=name)

    def get_provider_factory(self, key: ProviderKey, name: str | None = None) -> ProviderFactoryFn | None:
        if not self._deps_provider:
            return None
        return self._deps_provider.get_provider_factory(key, name=name)

    def get_resource[T](self, key: type, name: str | None = None) -> T | None:
        if not self._deps_provider:
            return None
        if instance := self._deps_provider.get_resource(key, name=name):
            return cast(T, instance)
        return None

    def set_resource(self, key: type, instance: Any, name: str | None = None) -> bool:
        if not self._deps_operator:
            return False
        return self._deps_operator.set_resource(key, instance, name=name)

    def reset_resource(self, key: type, name: str | None = None) -> bool:
        if not self._deps_operator:
            return False
        return self._deps_operator.reset_resource(key, name=name)

    @property
    def operator_factories(self) -> OperatorFactoryRegistry:
        return self._op_factories

    def create(self) -> Awaitable[Result] | Result:
        return Result.from_ok()

    def destroy(self) -> Awaitable[Result] | Result:
        return Result.from_ok()

    def start(self) -> Awaitable[None] | None:
        pass

    def stop(self) -> Awaitable[None] | None:
        pass

    def check_operator_mapping(
        self, policies: list['OperatorMappingPolicy'],
    ) -> Result[Mapping[OperatorKey, KeyMapping[OperatorKey]]]:
        policies_dict = {p.key: p for p in policies}
        key_mapping = {}

        for key in self._op_factories.list_keys():
            if key not in policies_dict:
                error = OpPolicyViolation(type='controller', name=self.name, key=str(key.__name__))
                logger.error(error.message)
                return Result.from_error(error)

            name_pattern = policies_dict[key].name_pattern

            if name_pattern is None:
                key_mapping[key] = KeyMapping[OperatorKey](map_to=policies_dict[key].map_to)
                policies_dict.pop(key)
                continue

            names = []

            for name in self._op_factories.get_names(key):
                if name_pattern.match(name):
                    names.append(name)

            if not names:
                error = NoOpFoundForPattern(
                    type='controller', name=self.name, key=str(key.__name__), pattern=name_pattern,
                )
                logger.error(error.message)
                return Result.from_error(error)

            key_mapping[key] = KeyMapping[OperatorKey](map_to=policies_dict[key].map_to, names=names)
            policies_dict.pop(key)

        for p in policies_dict.values():
            logger.warn(tr('app.entities.operator-unused-policy').format(
                type='controller', name=self.name, key=str(p.key.__name__), pattern=p.name_pattern or '-',
            ))

        return Result.from_ok(key_mapping)
