import json
from datetime import datetime
from logging import Logger
from memorycache import CacheManager
from string import upper

import msgbus
from mbcontextmessagehandler import MbContextMessageBus
from typing import (
    Any,
    Dict,
    Optional,
)

from messagebus import (
    DefaultToken,
    Message,
)
from model.config import Config
from model.error_type import ExternalServicesErrorsType
from model.exception import ExternalServiceErrors
from model.repository import PosRepositoryModel
from interactor.route.store.get_status.dto import (
    GetStoreResponse,
    GetStoreRequest,
    StatusResponse,
)
from model.tokens import BusTokens
from validation_util import validate_api_key

FISCAL_KEY = "fiscal"
PRINTER_KEY = "printer"
MANAGER_KEY = "manager"
MENU_KEY = "menu"
EFT_KEY = "eft"


class GetStoreInteractor(object):
    def __init__(
        self,
        logger,                 # type: Logger
        config,                 # type: Config
        pos_repository,         # type: PosRepositoryModel
        message_bus,            # type: MbContextMessageBus
        memory_cache,           # type: Dict[str, Any[Dict[str, CacheManager], CacheManager]]
    ):
        # type: (...) -> None

        self.logger = logger
        self.config = config
        self.pos_repository = pos_repository
        self.message_bus = message_bus
        self.memory_cache = memory_cache

    def execute(
        self,
        request,    # type: GetStoreRequest
    ):
        # type: (...) -> GetStoreResponse

        validate_api_key(request, self.config.api_key_mapping)

        menu_id = self.memory_cache[MENU_KEY].get_cached_object(new_object_func=self.pos_repository.get_catalog_version)
        if not menu_id:
            raise ExternalServiceErrors(ExternalServicesErrorsType.UnableToGetMenuId)

        fiscal = self.memory_cache[FISCAL_KEY].get_cached_object(new_object_func=self._fiscal_status)

        pos_id = str(request.pos_id)
        printer = self.memory_cache[PRINTER_KEY][pos_id].get_cached_object(self._get_printer_status, False, pos_id)
        eft = self.memory_cache[EFT_KEY][pos_id].get_cached_object(self._get_eft_status, False, pos_id)
        manager = self.memory_cache[MANAGER_KEY][pos_id].get_cached_object(self._get_manager_status, False, pos_id)

        return GetStoreResponse(
            menu_id=menu_id,
            printer=printer,
            fiscal=fiscal,
            eft=eft,
            manager=manager,
        )

    def _fiscal_status(
        self,
    ):
        # type: (...) -> StatusResponse

        status_fiscal = False
        if self.config.nf_type == 'NFCE':
            status_fiscal = self.pos_repository.get_sefaz_status()
        elif self.config.nf_type == 'SAT':
            status_fiscal = self.pos_repository.get_store_sat_status()

        return StatusResponse(
            status=status_fiscal,
            message="OK" if status_fiscal else "Erro Fiscal",
        )

    def _get_printer_status(
        self,
        pos_id,     # type: int
    ):
        # type: (...) -> StatusResponse

        try:
            device_name = self._get_device_manager_info(
                pos_id=pos_id,
                device_type="printer",
            )
            if device_name is None:
                raise Exception("Printer unavailable")

            message_printer = Message(
                token=msgbus.TK_PRN_GETSTATUS,
                data_type=msgbus.FM_PARAM,
                data=None,
            )
            msg_printer = self.message_bus.send_message(
                component=device_name,
                message=message_printer,
            )

            if msg_printer.token == DefaultToken.TK_SYS_ACK and msg_printer.data == "0":
                return StatusResponse(
                    status=True,
                    message="OK",
                )

            return StatusResponse(
                status=False,
                message="NOK",
            )
        except Exception:
            return StatusResponse(
                status=False,
                message="NOK",
            )

    def _get_eft_status(
        self,
        pos_id,     # type: int
    ):
        # type: (...) -> StatusResponse

        try:
            device_name = self._get_device_manager_info(
                pos_id=pos_id,
                device_type="eft",
            )
            if device_name is None:
                raise Exception("Eft unavailable")

            data = "{};{};{}".format(pos_id, datetime.now().strftime("%Y%m%d"), datetime.now().strftime('%H:%M:%S'))
            message = Message(
                token=BusTokens.TK_ELECTRONIC_PAYMENT_CONNECTIVITY_TEST,
                data_type=msgbus.FM_PARAM,
                data=data,
            )
            msg = self.message_bus.send_message(
                component=device_name,
                message=message,
            )

            if msg.token == DefaultToken.TK_SYS_ACK and upper(msg.data) == "TRUE":
                return StatusResponse(
                    status=True,
                    message="OK",
                )

            return StatusResponse(
                status=False,
                message="NOK",
            )

        except Exception:
            return StatusResponse(
                status=False,
                message="NOK",
            )

    def _get_manager_status(
        self,
        pos_id,     # type: int
    ):
        # type: (...) -> StatusResponse

        totem_was_closed = self.pos_repository.get_manager_status(pos_id=pos_id)
        model = self.pos_repository.get_model(pos_id=pos_id)
        message = self.pos_repository.translate_message(
            model=model,
            message="CLOSED" if totem_was_closed else "OPENED",
        )
        return StatusResponse(
            status=not totem_was_closed,
            message=message,
        )

    def _get_device_manager_info(
        self,
        pos_id,         # type: int
        device_type,    # type: str
    ):
        # type: (...) -> Optional[str]

        request = json.dumps(
            {
                "posId": pos_id,
                "deviceType": device_type
            }
        )
        message = Message(
            token=BusTokens.TK_GET_USED_SERVICE,
            data_type=msgbus.FM_PARAM,
            data=request,
        )
        msg = self.message_bus.send_message(
            component="DeviceManager",
            message=message,
        )

        if msg.token == DefaultToken.TK_SYS_ACK:
            return msg.data

        return None

