import logging
from typing import Type, Sequence, Optional

from clarabot_nano.common.handler.base import AbstractNanoHandler, NanoHandlerError
from clarabot_nano.common.protocol.errors import (
    NANO_REQUEST_ERROR_UNKNOWN_TYPE,
    NANO_REQUEST_ERROR_CONFLICT,
    NANO_REQUEST_ERROR,
)
from clarabot_nano.common.protocol.request import NanoBaseRequest
from clarabot_nano.common.protocol.server import NanoServerResponse
from clarabot_nano.utility.handlers import HandlerClassRegistry

logger = logging.getLogger(__name__)
access_logger = logging.getLogger("nano/access-log")


class NanoHandlerClassRegistry(HandlerClassRegistry):
    async def invoke(self, helper, extracted_config, context, payload):
        handler_type = None
        # Request is parsed, select handler and execute.
        try:
            handler_type = payload.get(NanoBaseRequest.TYPE)
            try:
                handler_cls: Optional[Type[AbstractNanoHandler]] = self.get_handler_cls(handler_type)
            except TypeError:  # not hashable
                handler_cls = None
            if not handler_cls:
                raise NanoHandlerError(NANO_REQUEST_ERROR_UNKNOWN_TYPE, f"Unknown handler ({handler_type})", context)
            # freeze a reference to the extracted config for the handler to use
            handler = handler_cls(helper, extracted_config, context, payload)
            response = await handler.execute()  # TODO: timeout?
        except NanoHandlerError as e:
            if e.code == NANO_REQUEST_ERROR_CONFLICT:
                # Parallel editing/datasource-changed conflicts are errors for the transfer, but are expected to
                # happen from time to time. Log them on info level because they are temporary issues and are
                # automatically resolved.
                access_logger.info(e.log)
            else:
                access_logger.warning(e.log)
            response = {NanoServerResponse.ERROR: e.code}
        except Exception:
            log_msg = NanoHandlerError.format_message(
                NANO_REQUEST_ERROR, f"Unexpected error by handler ({handler_type})", context
            )
            access_logger.error(log_msg)
            logger.exception(log_msg)
            response = {NanoServerResponse.ERROR: NANO_REQUEST_ERROR}
        else:
            access_logger.log(handler.SUCCESS_LOG_LEVEL, handler.success_log_message())
        return response


# class PrefixedNanoHandlerClassRegistry(NanoHandlerClassRegistry):
#     def __init__(self, prefix: str, handlers: Sequence[Type[AbstractNanoHandler]] = ()):
#         self.prefix = prefix
#         NanoHandlerClassRegistry.__init__(self, handlers)
#
#     def _key_from_handler_cls(self, cls: Type[AbstractNanoHandler]) -> str:
#         return f"{self.prefix}.{NanoHandlerClassRegistry._key_from_handler_cls(self, cls)}"
