from logging import Logger
from typing import (
    Optional,
    List,
)

from interactor.route.payment.insert.dto import (
    OrderInsertPaymentRequest,
    OrderInsertPaymentResponse,
)
from model.config import (
    Config,
    MapValidatePosId,
)
from model.error_type import (
    ExternalServicesErrorsType,
)
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_payment_type_is_valid,
    check_if_payment_amount_is_valid,
    validate_pos_id,
)


payment_types_cache = None


class OrderInsertPaymentInteractor(object):
    def __init__(
        self,
        logger,             # type: Logger
        config,             # type: Config
        pos_repository,     # type: PosRepositoryModel
        fiscal_service,     # type: FiscalService
        payment_service     # type: Optional[PaymentService]
    ):
        # type: (...) -> None

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

    def execute(
        self,
        request,     # type: OrderInsertPaymentRequest
    ):
        # type: (...) -> OrderInsertPaymentResponse

        model_validate = MapValidatePosId(
            pos_id=request.pos_id,
            system_api_key=request.system_api_key,
            secure_api_key_list=self.config.api_key_mapping,
        )

        pos_id = validate_pos_id(
            request=model_validate,
        )

        model = self.pos_repository.get_model(pos_id)
        pos_ot = self.pos_repository.get_pos_ot(
            model=model,
        )
        request = self._change_to_correct_electronic_type_id(request)
        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_payment_type_is_valid(self.config.allowed_payment_methods, request.electronic_type_id)
        check_if_payment_amount_is_valid(request.amount, model)

        if request.is_register_payment:
            tender_details = self.payment_service.get_tender_details(
                payment_data=request,
            )
        else:
            try:
                tender_details = self.fiscal_service.process_payment(model, request)
            except Exception as _:
                self.logger.exception("Error performing payment")
                raise ExternalServiceErrors(ExternalServicesErrorsType.InsertPaymentUnavailable)

        tender = self.pos_repository.do_tender(
            pos_id=pos_id,
            tender_id=request.payment_id,
            pos_ot=pos_ot,
            amount=request.amount,
            tender_details=tender_details,
        )

        return OrderInsertPaymentResponse(tender)

    def _change_to_correct_electronic_type_id(
        self,
        request     # type: OrderInsertPaymentRequest
    ):
        # type: (...) -> OrderInsertPaymentRequest

        global payment_types_cache

        if not self.payment_service:
            return request

        if not payment_types_cache:
            payment_types = self.payment_service.get_payment_types()
            if not payment_types:
                return request
            payment_types_cache = payment_types

        request.electronic_type_id = self._filter_electronic_type_id(
            payment_types_cache,
            request.payment_id
        )

        return request

    @staticmethod
    def _filter_electronic_type_id(
        payment_types,   # type: List[PaymentType]
        default_value,   # type: int
    ):
        # type: (...) -> int

        return next(
            (item.electronic_type_id for item in payment_types if item.payment_type == default_value),
            default_value
        )
