import json

from typing import List, Dict

from mbcontextmessagehandler import MbContextMessageBus
from mwhelper import BaseRepository
from persistence import Connection

from interactor.route.payment.insert.dto import OrderInsertPaymentRequest
from model.config import Config
from model.error_type import PosValidationErrorsType
from model.exception import ValidationError
from model.payment import PaymentType
from model.services import PaymentServiceModel
from model.repository import PosRepositoryModel

VOUCHER = 3
DEBIT = 2


class PaymentService(PaymentServiceModel, BaseRepository):

    def __init__(
        self,
        message_bus,        # type: MbContextMessageBus
        config,             # type: Config
        pos_repository,     # type: PosRepositoryModel
    ):
        # type: (...) -> None

        super(PaymentService, self).__init__(message_bus.mbcontext)
        self.message_bus = message_bus
        self.config = config
        self.payment_types = None
        self.pos_repository = pos_repository

    def get_payment_types(
        self
    ):
        # type: () -> List[PaymentType]

        def inner_func(conn):
            # type: (Connection) -> List[int]

            query = """
                SELECT
                    TT.TenderId AS TenderId,
                    TT.TenderDescr AS TenderDescr,
                    TT.ElectronicType AS ElectronicTypeId,
                    TET.TypeDescr AS ElectronicTypeDescr,
                    TT.ParentTenderId AS ParentTenderId,
                    TT.CustomAttr As CustomAttr
                FROM productdb.TenderType TT
                JOIN productdb.TenderElectronicType TET ON TET.TypeId=TT.ElectronicType;
            """
            cursor = conn.select(query)

            tenders = {}
            for row in cursor:
                tender_id = int(row.get_entry('TenderId'))
                parent_tender_id = row.get_entry('ParentTenderId')
                tenders[tender_id] = {
                    'id': tender_id,
                    'descr': row.get_entry('TenderDescr'),
                    'electronicType': row.get_entry('ElectronicTypeDescr'),
                    'electronicTypeId': int(row.get_entry('ElectronicTypeId')),
                    'parentId ': int(parent_tender_id) if parent_tender_id else None,
                    'customAttr': row.get_entry('CustomAttr')
                }

            return tenders

        if not self.payment_types:
            self.payment_types = self.format_payment_types(self.execute_with_connection(
                inner_func,
                service_name="Persistence"
            ))

        return self.payment_types

    def format_payment_types(
        self,
        tender_types,    # type: List[Dict[int, str]]
    ):
        # type: (...) -> List[PaymentType]

        payment_types = []
        config_file = open(self.config.totem_config_path, 'rb')
        data = json.load(config_file)
        payments = data.get("payments", {})
        tenders = [tender for _, tender in tender_types.items()]

        for key, payment in payments.items():
            electronic_type_json_id = int(key)
            tender = [tender for tender in tenders if electronic_type_json_id == tender.get("electronicTypeId", -1)]
            payment_type_id = None

            if len(tender) == 0:
                if electronic_type_json_id != VOUCHER:
                    continue

                payment_type_id = DEBIT
                electronic_type_json_id = DEBIT

            if payment_type_id is None:
                payment_type_id = tender[0].get("id")

            payment_name = payment.get("paymentName", {})
            payment_name = self._get_payment_names(payment_name=payment_name)
            image_path = payment.get("name", "")

            payment_type = PaymentType(
                payment_type=payment_type_id,
                payment_name=payment_name,
                image_path=image_path,
                electronic_type_id=electronic_type_json_id,
            )

            payment_types.append(payment_type)

        if not payment_types:
            raise ValidationError(PosValidationErrorsType.PaymentTypesToShowNotFound)

        return sorted(payment_types, key=lambda p: p.payment_type)

    def get_tender_details(
        self,
        payment_data,   # type: OrderInsertPaymentRequest
    ):
        # type: (...) -> str

        tender_details = json.dumps({
            "Media": self._validate_required_property(
                property_name="media",
                property_value=payment_data.media,
                check_required_property=True,
            ),
            "CNPJAuth": self._validate_required_property(
                property_name="cnpj_auth",
                property_value=payment_data.cnpj_auth,
                check_required_property=True,
            ),
            "TransactionProcessor": self._validate_required_property(
                property_name="transaction_processor",
                property_value=payment_data.transaction_processor,
                check_required_property=True,
            ),
            "Bandeira": self._validate_required_property(
                property_name="brand",
                property_value=payment_data.brand,
                check_required_property=True,
            ),
            "IdAuth": self._validate_required_property(
                property_name="id_auth",
                property_value=payment_data.id_auth,
                check_required_property=True,
            ),
            "AuthCode": self._validate_required_property(
                property_name="auth_code",
                property_value=payment_data.auth_code,
                check_required_property=True,
            ),
            "ReceiptCustomer": self._validate_required_property(
                property_name="receipt_customer",
                property_value=payment_data.receipt_customer,
                check_required_property=True,
            ),
            "ReceiptMerchant": self._validate_required_property(
                property_name="receipt_merchant",
                property_value=payment_data.receipt_merchant,
                check_required_property=True,
            ),
            "AccreditorSat": self._validate_required_property(
                property_name="accreditor_sat",
                property_value=payment_data.accreditor_sat,
                check_required_property=False,
            ),
            "tBand": self._validate_required_property(
                property_name="t_band",
                property_value=payment_data.t_band,
                check_required_property=False,
            ),
            "tPag": self._validate_required_property(
                property_name="t_pag",
                property_value=payment_data.t_pag,
                check_required_property=False,
            ),
            "NsuHost": self._validate_required_property(
                property_name="nsu_host",
                property_value=payment_data.nsu_host,
                check_required_property=False,
            ),
            "CardNumber": self._validate_required_property(
                property_name="card_number",
                property_value=payment_data.card_number,
                check_required_property=False,
            ),
            "TransactionIdentifier":  self._validate_required_property(
                property_name="transaction_identifier",
                property_value=payment_data.transaction_identifier,
                check_required_property=True,
            )
        })

        return tender_details

    @staticmethod
    def _validate_required_property(
        property_name,                      # type: str
        property_value,                     # type: str
        check_required_property=False,      # type: bool
    ):
        # type: (...)-> str

        if check_required_property and property_value in [None, ""]:
            raise ValidationError(
                PosValidationErrorsType.PropertyRequiredNotFound,
                property_name,
            )

        return property_value

    def _get_payment_names(
        self,
        payment_name,   # type: Dict
    ):
        # type: (...) -> Dict

        languages = self.pos_repository.get_totem_languages()
        payment_name_dict = {}

        for language in languages:
            payment_name_dict.update(
                {language: payment_name.get(language) or ''}
            )

        return payment_name_dict
