from typing import Type, Optional, Callable, Union

from messageprocessor import MessageProcessor
from simplehttprouter import Route

from ._OpenApiModelBuilder import OpenApiModelBuilder
from .dto import RouteBuildParams
from ..model import PathModel, ObjectSchemaModel, BasePropertySchemaModel
from ..utils import set_dict_path_value, get_dict_path_value

_BUILD_PATH_PARAMS_CALLBACK_TYPE = Callable[
    [OpenApiModelBuilder, Route, Union[MessageProcessor, Type[MessageProcessor]]],
    RouteBuildParams
]
_CREATE_PATH_CALLBACK_TYPE = Callable[
    [OpenApiModelBuilder, RouteBuildParams],
    PathModel
]
_UPDATE_PATH_CALLBACK_TYPE = Callable[
    [OpenApiModelBuilder, RouteBuildParams, PathModel],
    PathModel
]


class PathModelDescriptor:
    def __init__(
        self,
        build_params_callback: Optional[_BUILD_PATH_PARAMS_CALLBACK_TYPE] = None,
        create_callback: Optional[_CREATE_PATH_CALLBACK_TYPE] = None,
        update_callback: Optional[_UPDATE_PATH_CALLBACK_TYPE] = None,
    ):
        self._build_params_callback = build_params_callback
        self._create_callback = create_callback
        self._update_callback = update_callback
        self._available = True

    def is_available(self) -> bool:
        return self._available

    def build_params(
        self,
        builder: OpenApiModelBuilder,
        route: Route,
        processor: Union[MessageProcessor, Type[MessageProcessor]]
    ) -> RouteBuildParams:
        self._available = False
        try:
            if self._create_callback is not None:
                return self._build_params_callback(builder, route, processor)
            return builder.build_processor_params(
                route=route,
                processor=processor,
            )
        finally:
            self._available = True

    def create_path(self, builder: OpenApiModelBuilder, parameters: RouteBuildParams) -> PathModel:
        self._available = False
        try:
            if self._create_callback is not None:
                return self._create_callback(builder, parameters)
            return builder.add_interactor_path(parameters)
        finally:
            self._available = True

    def update_path(
        self,
        builder: OpenApiModelBuilder,
        parameters: RouteBuildParams,
        path_model: PathModel,
    ) -> PathModel:
        self._available = False
        try:
            if self._update_callback is not None:
                return self._update_callback(builder, parameters, path_model)
            return path_model
        finally:
            self._available = True


def path_descriptor(
    build_params: Optional[_BUILD_PATH_PARAMS_CALLBACK_TYPE] = None,
    create: Optional[_CREATE_PATH_CALLBACK_TYPE] = None,
    update: Optional[_UPDATE_PATH_CALLBACK_TYPE] = None,
) -> PathModelDescriptor:
    return PathModelDescriptor(
        build_params_callback=build_params,
        create_callback=create,
        update_callback=update,
    )


_CREATE_PAYLOAD_CALLBACK_TYPE = Callable[
    [OpenApiModelBuilder, RouteBuildParams, Optional[PathModel]],
    BasePropertySchemaModel
]
_UPDATE_PAYLOAD_CALLBACK_TYPE = Callable[
    [OpenApiModelBuilder, RouteBuildParams, ObjectSchemaModel, Optional[PathModel]],
    BasePropertySchemaModel
]


class PayloadDescriptor:
    def __init__(
        self,
        create_callback: Optional[_CREATE_PAYLOAD_CALLBACK_TYPE] = None,
        update_callback: Optional[_UPDATE_PAYLOAD_CALLBACK_TYPE] = None,
    ):
        self._create_callback = create_callback
        self._update_callback = update_callback
        self._available = True

    def is_available(self) -> bool:
        return self._available

    def create_object(
        self,
        builder: OpenApiModelBuilder,
        parameters: RouteBuildParams,
        path_model: Optional[PathModel] = None,
    ) -> BasePropertySchemaModel:
        self._available = False
        try:
            if self._create_callback is not None:
                return self._create_callback(builder, parameters, path_model)
            return builder.build_object_model(
                parameters=parameters,
                request_type=parameters['request_type'],
                path_model=path_model,
            )
        finally:
            self._available = True

    def update_object(
        self,
        builder: OpenApiModelBuilder,
        parameters: RouteBuildParams,
        object_model: ObjectSchemaModel,
        path_model: Optional[PathModel] = None,
    ) -> BasePropertySchemaModel:
        self._available = False
        try:
            if self._update_callback is not None:
                return self._update_callback(builder, parameters, object_model, path_model)
            return object_model
        finally:
            self._available = True


def payload_descriptor(
    create: Optional[_CREATE_PAYLOAD_CALLBACK_TYPE] = None,
    update: Optional[_UPDATE_PAYLOAD_CALLBACK_TYPE] = None,
) -> PayloadDescriptor:
    return PayloadDescriptor(
        create_callback=create,
        update_callback=update,
    )


class Descriptors:
    def __init__(self):
        self.descriptors = {}

    def add_route_descriptor(self, route: Route, descriptor: PathModelDescriptor):
        set_dict_path_value(
            dict_value=self.descriptors,
            path=['route', route],
            value=descriptor,
        )

    def add_processor_descriptor(self, processor_type: Type[MessageProcessor], descriptor: PathModelDescriptor):
        set_dict_path_value(
            dict_value=self.descriptors,
            path=['processor', processor_type],
            value=descriptor,
        )

    def add_interactor_descriptor(self, interactor_type: Type, descriptor: PathModelDescriptor):
        set_dict_path_value(
            dict_value=self.descriptors,
            path=['interactor', interactor_type],
            value=descriptor,
        )

    def add_payload_descriptor(self, payload_type: Type, descriptor: PayloadDescriptor):
        set_dict_path_value(
            dict_value=self.descriptors,
            path=['payload', payload_type],
            value=descriptor,
        )

    def get_route_descriptor(self, route: Route) -> Optional[PathModelDescriptor]:
        return get_dict_path_value(
            dict_value=self.descriptors,
            path=['route', route],
        )

    def get_interactor_descriptor(self, interactor_type: Type) -> Optional[PathModelDescriptor]:
        return get_dict_path_value(
            dict_value=self.descriptors,
            path=['interactor', interactor_type],
        )

    def get_processor_descriptor(self, processor_type: Type[MessageProcessor]) -> Optional[PathModelDescriptor]:
        return get_dict_path_value(
            dict_value=self.descriptors,
            path=['processor', processor_type],
        )

    def get_payload_descriptor(self, payload_type: Type) -> Optional[PayloadDescriptor]:
        return get_dict_path_value(
            dict_value=self.descriptors,
            path=['payload', payload_type],
        )
