import base64
from logging import Logger
from threading import Lock

from model.error_type import ExternalServicesErrorsType
from typing import Any

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 validation_util import (
    check_if_business_day_is_opened,
    check_if_operator_is_logged,
    check_if_order_is_totaled,
    check_if_due_amount_is_zero,
    validate_api_key,
)

order_finalize_lock = {}


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

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

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

        pos_id = validate_api_key(request, self.config.api_key_mapping)
        with self._get_pos_lock(pos_id):
            model = self.pos_repository.get_model(pos_id)

            check_if_business_day_is_opened(self.pos_repository, model)
            check_if_operator_is_logged(self.pos_repository, model)
            check_if_order_is_totaled(self.pos_repository, model)
            check_if_due_amount_is_zero(model)

            pos_ot = self.pos_repository.get_pos_ot(model)
            try:
                response = self.fiscal_service.process_order_fiscal(
                    model=model,
                    pos_ot=pos_ot,
                    print_coupon=request.print_coupon,
                )
            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.pos_repository.set_order_to_paid(model, pos_ot)

            return FiscalOrderResponse(
                fiscal_coupon=base64.b64encode(response),
            )

    @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]
