import base64
from logging import Logger
from threading import Lock

from model.error_type import ExternalServicesErrorsType

from interactor.route.order.finalize.dto import (
    OrderFinalizeRequest,
    FiscalOrderResponse,
)
from model.config import Config
from model.exception import ExternalServiceErrors
from model.repository import PosRepositoryModel
from services import FiscalService, PrinterService
from validation_util import (
    check_if_business_day_is_opened,
    check_if_operator_is_logged,
    check_if_order_is_totaled,
    validate_api_key,
    check_if_request_order_id_is_equal_current_order_id,
    check_if_state_and_due_amount_is_valid_to_finalize,
)

order_finalize_lock = {}


class OrderFinalizeInteractor(object):
    def __init__(
        self,
        logger,              # type: Logger
        config,              # type: Config
        pos_repository,      # type: PosRepositoryModel
        fiscal_service,      # type: FiscalService
        printer_service,     # type: PrinterService
    ):
        # type: (...) -> None

        self.logger = logger
        self.config = config
        self.pos_repository = pos_repository
        self.fiscal_service = fiscal_service
        self.printer_service = printer_service

    def execute(
        self,
        request,    # type: OrderFinalizeRequest
    ):
        # type: (...) -> FiscalOrderResponse

        pos_id = validate_api_key(request, self.config.api_key_mapping)
        order_id = request.order_id

        self.logger.info("Executing finalize order: [{}-{}]".format(request.pos_id, order_id))
        with self._get_pos_lock(pos_id):
            self.logger.info("Locking POS to process order: [{}-{}]".format(request.pos_id, order_id))

            model = self.pos_repository.get_model(pos_id)

            check_if_request_order_id_is_equal_current_order_id(
                model=model,
                request_order_id=order_id,
            )
            check_if_business_day_is_opened(self.pos_repository, model)
            check_if_operator_is_logged(self.pos_repository, model)
            check_if_state_and_due_amount_is_valid_to_finalize(
                logger=self.logger,
                pos_id=request.pos_id,
                order_id=order_id,
                model=model,
                pos_repository=self.pos_repository,
            )

            pos_ot = self.pos_repository.get_pos_ot(model)
            try:
                self.logger.info("Processing order fiscal: [{}-{}]".format(request.pos_id, order_id))
                response = self.fiscal_service.process_order_fiscal(
                    model=model,
                    print_coupon=request.print_coupon,
                    device_type=request.device_type,
                )
            except Exception as _:
                self.pos_repository.void_tenders_and_order(pos_id, pos_ot)
                self.logger.exception("Error fiscalizing order. The order has been voided")
                raise ExternalServiceErrors(ExternalServicesErrorsType.FiscalizeUnavailable)

            self.logger.info("Set order to paid: [{}-{}]".format(request.pos_id, order_id))
            self.pos_repository.set_order_to_paid(
                pos_id=pos_id,
                order_id=order_id,
                model=model,
                pos_ot=pos_ot,
            )

            self.logger.info("Get order customer receipts: [{}-{}]".format(request.pos_id, order_id))
            customer_receipts = self.fiscal_service.get_customer_receipts(
                model=model,
            )

            if request.print_tef_customer_receipt:
                self.logger.info("Send order to print: [{}-{}]".format(request.pos_id, order_id))
                for tef_customer_receipt in customer_receipts:
                    self.printer_service.send_print_data(
                        model=model,
                        response=base64.b64decode(tef_customer_receipt),
                    )

            self.logger.info("Release lock order: [{}-{}]".format(request.pos_id, order_id))
            return FiscalOrderResponse(
                fiscal_coupon=base64.b64encode(response) if response else "",
                tef_customer_receipt=customer_receipts,
            )

    @staticmethod
    def _get_pos_lock(
            pos_id,     # type: str
    ):
        # type: (...) -> Lock

        global order_finalize_lock

        if pos_id not in order_finalize_lock:
            order_finalize_lock[pos_id] = Lock()

        return order_finalize_lock[pos_id]
