import json
import logging
import time

from logging import Logger

from typing import Optional
from production.service import PrinterService

from messagebus import (
    DataType,
    DefaultToken,
    Message,
    MessageBus,
    TokenCreator,
    TokenPriority
)
from memorycache import (
    CacheManager,
    PeriodBasedExpiration,
)

PERIPHERALS_GROUP = "6"
MSGGRP_DEVICE_MANAGER = "D"
TK_PRN_PRINT = TokenCreator.create_token(TokenPriority.high, PERIPHERALS_GROUP, "101")
TK_GET_USED_SERVICE = TokenCreator.create_token(TokenPriority.low, MSGGRP_DEVICE_MANAGER, "1")

MAX_RETRY = 10

printer_logger = logging.getLogger("ProductionPrinter")


class PickListPrinter(object):
    def __init__(
        self,                           # type: PickListPrinter
        message_bus,                    # type: MessageBus
        timeout,                        # type: int
        name,                           # type: str
        logger,                         # type: Logger
        timeout_to_cleanup_devices,     # type: int
        kds_id="",                      # type: Optional[str]
        printer="",                     # type: Optional[str]
    ):
        # type: (...) -> None

        self.name = name
        self.logger = logger
        self.message_bus = message_bus
        self.timeout = timeout
        self.printer = printer
        self.cache_devices = CacheManager(
            cache_expiration=PeriodBasedExpiration(period_in_minutes=timeout_to_cleanup_devices),
            new_object_func=self._renew_cache_devices,
        )
        self.printer_service = PrinterService(
            message_bus=message_bus,
            timeout=self.timeout,
        )
        self.kds_id = kds_id

    def clear_printer_cache(
        self,   # type: PickListPrinter
    ):
        # type: (...) -> None

        self.cache_devices.force_renew_cache()

    def print_report(
        self,   # type: PickListPrinter
        text,   # type: str
    ):
        # type: (...) -> bool

        for attempt in range(1, MAX_RETRY):
            printer_name = self._get_available_printer()
            try:
                printer_logger.info("Sending to printer: {} - Attempt: {}".format(printer_name, attempt))

                if attempt == 1:
                    printer_logger.info("Printer: {} - \nPrinting:\n {}".format(printer_name, text))

                reply = self.printer_service.send_to_printer(
                    printer_name=printer_name,
                    text=text,
                )
                if reply.token != DefaultToken.TK_SYS_ACK:
                    log_message = "Invalid response from Printer: {} / {} - {}"
                    self.clear_printer_cache()
                    printer_logger.warning(log_message.format(printer_name, reply.token, reply.data))
                else:
                    printer_logger.info("Successfully printed on Printer: {}".format(printer_name))
                    return True
            except BaseException as _:
                if attempt >= MAX_RETRY - 1:
                    log_message = "Printer {} unavailable - Failed to Print Report"
                    printer_logger.warning(
                        log_message.format(printer_name),
                        exc_info=True,
                    )
                else:
                    log_message = "Error when trying to print on Printer {} in attempt {} - {}"
                    if printer_name is None and self.kds_id:
                        additional_info = "All Configured Printers for KDS ID {} are disabled".format(self.kds_id)
                    else:
                        additional_info = "Tried to print on printer {} but failed".format(printer_name)
                    printer_logger.info(
                        log_message.format(printer_name, attempt, additional_info),
                        exc_info=True,
                    )

            time.sleep(2 * attempt)

        return False

    def _renew_cache_devices(
        self,    # type: PickListPrinter
    ):
        # type: (...) -> str

        if not self.kds_id:
            return self.printer

        data = json.dumps({
            "kdsId": int(self.kds_id),
            "deviceType": "printer",
        })
        message = Message(
            token=TK_GET_USED_SERVICE,
            data_type=DataType.param,
            data=data,
            timeout=self.timeout,
        )
        try:
            response = self.message_bus.send_message(
                component='DeviceManager',
                message=message,
            )

            if response.token != DefaultToken.TK_SYS_ACK:
                self.logger.error("Printer not found, saving {} as KDS {} printer. Received message: {}".format(
                    self.printer,
                    self.kds_id,
                    response.data,
                ))
                updated_printer = self.printer
            else:
                updated_printer = response.data or self.printer
                self.logger.info("Saving {} as KDS {} printer".format(updated_printer, self.kds_id))
        except Exception as _:
            self.logger.error(
                "Error sending message for DeviceManager for KdsId {}".format(self.kds_id),
                exc_info=True,
            )
            return self.printer

        return updated_printer

    def _get_available_printer(
        self,   # type: PickListPrinter
    ):
        # type: (...) -> str

        return self.cache_devices.get_cached_object()
