import datetime

from abc import ABCMeta

from typing import (
    Dict,
    Optional,
    List,
    Any,
)
from xml.etree import cElementTree as eTree

from _AposA8Align import AposA8Align
from _AposA8CommandType import AposA8CommandType
from _AposA8PrintLine import AposA8PrintLine
from _AposA8Helpers import (
    append_items_header,
    append_and_count_sale_items,
    APOS_DASH,
    APOS_NEWLINE,
    append_coupon_fiscal_parameters,
    build_call_id,
    get_text_line,
    format_text,
    split_lines,
    append_lines_from_array,
)
from common import PrinterCommon
from printer import FiscalPrinter
from msgbus import MBEasyContext

from clichehelper import ClicheHelper

from _AposA8FontSize import AposA8FontSize
from _AposA8Constants import AposA8Constants
from printer_wrapper import insert_space
from sysactions import translate_message
from models import LoyaltyConfigurations
from services import (
    get_fee_details,
    get_order_fees,
    order_xml_has_valid_fee,
)

AMOUNT_TEMPLATE_SMALL = "%-36s   R$%7.2f"
AMOUNT_TEMPLATE_LARGE = "%-20s   R$%7.2f"


class AposA8FiscalPrinter(FiscalPrinter):
    __metaclass__ = ABCMeta

    def __init__(
        self,
        mb_context,                     # type: MBEasyContext
        order_ref_ids,                  # type: list
        cliche_helper,                  # type: ClicheHelper
        customer_text_field,            # type: str
        coupon_fiscal_parameters,       # type: Dict[str, bool]
        font_size_withdrawal_panel,     # type: str
        danfe_font_size_mobile,         # type: str
        order_ref_position,             # type: str
        loyalty_configurations,         # type: LoyaltyConfigurations
    ):
        # type: (...) -> None

        super(AposA8FiscalPrinter, self).__init__(
            mb_context=mb_context,
            order_ref_ids=order_ref_ids,
            cliche_helper=cliche_helper,
            coupon_fiscal_parameters=coupon_fiscal_parameters,
            order_ref_position=order_ref_position,
            loyalty_configurations=loyalty_configurations,
        )

        self.customer_text_field = customer_text_field
        self.font_size_withdrawal_panel = font_size_withdrawal_panel
        self.danfe_font_size_mobile = danfe_font_size_mobile

    def print_cancel(self, posid, data):
        # not implemented
        pass

    def _build_loyalty_report(
        self,                    # type: AposA8FiscalPrinter
        model,                   # type: eTree.Element
        qr_code_text,            # type: str
        protocol_date,           # type: datetime
    ):
        # type: (...) -> List

        report = [
            APOS_NEWLINE,
            AposA8PrintLine(
                align=AposA8Align.CENTER,
                text=qr_code_text,
                command_type=AposA8CommandType.PRINT_QR_CODE,
            ),
            APOS_NEWLINE,
        ]

        if self.loyalty_configurations.text_to_print_in_loyalty_coupon:
            split_lines(
                report=report,
                text=self.loyalty_configurations.text_to_print_in_loyalty_coupon.encode("utf-8"),
                font_size=self.font_size_withdrawal_panel,
            )
            report.append(APOS_NEWLINE)
            report.append(APOS_NEWLINE)

        if self.loyalty_configurations.store_name:
            report.append(
                AposA8PrintLine(
                    align=AposA8Align.CENTER,
                    text="{}: {}".format(
                        translate_message(
                            model=model,
                            message_key="LABEL_STORE",
                        ),
                        self.loyalty_configurations.store_name,
                    ),
                    command_type=AposA8CommandType.PRINT_TEXT,
                    font_size=self.font_size_withdrawal_panel,
                )
            )
            report.append(APOS_NEWLINE)

        report.append(
            AposA8PrintLine(
                align=AposA8Align.CENTER,
                text="{}: {}".format(
                    translate_message(
                        model=model,
                        message_key="ISSUED_AT",
                    ),
                    protocol_date.strftime("%d/%m/%Y %H:%M:%S"),
                ),
                command_type=AposA8CommandType.PRINT_TEXT,
                font_size=self.font_size_withdrawal_panel,
            )
        )
        report.append(APOS_NEWLINE)

        return report

    def _append_total(
        self,
        report,         # type: List[AposA8PrintLine]
        order,          # type: eTree
        tenders,        # type: List[Any]
        items_amount,   # type: int
    ):
        # type: (...) -> None

        is_small_font = self.danfe_font_size_mobile == AposA8FontSize.SMALL

        cols_items_amount = 3
        column_width = self._get_column_width(subtract=cols_items_amount)
        label = format_text(
            text="QTD. TOTAL DE ITENS",
            width=column_width,
        )

        items_amount = format_text(
            text=("%3d" % items_amount).strip(),
            width=3,
            align='>'
        )

        qty_total = "{}{}".format(label, items_amount)
        report.append(self._get_text_line(text=qty_total))

        amount_template = AMOUNT_TEMPLATE_SMALL if is_small_font else AMOUNT_TEMPLATE_LARGE

        if order_xml_has_valid_fee(order_xml=order):
            order_fees = get_order_fees(order_xml=order)
            for fee in order_fees:
                fee_details = get_fee_details(fee_code=fee.code)

                fee_amount_text = amount_template % (fee_details.product_name.upper(), fee.amount)
                report.append(self._get_text_line(text=fee_amount_text))

        total_amount_value = float(order.get("totalAmount")) + float(order.get("taxTotal")) + float(order.get("tip", 0))

        total_amount_text = amount_template % ("VALOR TOTAL R$", total_amount_value)
        report.append(self._get_text_line(text=total_amount_text))

        discount_amount_value = float(order.get("discountAmount"))
        discount = amount_template % ("DESCONTO", discount_amount_value)
        report.append(self._get_text_line(text=discount))

        total_payable = amount_template % ("VALOR A PAGAR", total_amount_value - discount_amount_value)
        report.append(self._get_text_line(text=total_payable))

        label_value = "Valor Pago"
        column_width = self._get_column_width(subtract=len(label_value))

        label = format_text(
            text="FORMA DE PAGAMENTO",
            width=column_width,
        )

        payment = "{}{}".format(label, label_value)

        report.append(self._get_text_line(text=payment))

        for tender in tenders:
            tender_descr = self.get_payment_description(self.mb_context, tender)
            tender_amount = tender.amount
            tender_amount = ("R$%7.2f" % tender_amount).encode("utf-8")
            column_width = self._get_column_width(subtract=len(tender_amount))

            label = format_text(
                text=tender_descr,
                width=column_width,
            )

            tender_text = "{}{}".format(label, tender_amount)

            report.append(self._get_text_line(text=tender_text))

        # Get "Change" (sixth value of tender tuple) from last tender in tenders
        change = tenders[len(tenders) - 1].change
        if change > 0:
            order_voided = order.get("state") == "VOIDED"
            factor = -1 if order_voided else 1

            change_text = amount_template % ("Troco", change * factor)
            report.append(self._get_text_line(text=change_text))

    def _append_customer_text_field(
        self,
        report,     # type: List[AposA8PrintLine]
    ):
        # type: (...) -> None

        if self.customer_text_field != "":
            for customer_text in self.customer_text_field.split('\\n'):
                report.append(self._get_text_line(text=customer_text.replace("\\n", "")))

            report.append(APOS_DASH)

    def _append_satisfaction_survey(
        self,
        report,                          # type: List[AposA8PrintLine]
        model,                           # type: eTree
        satisfaction_survey_code,        # type: str
    ):
        # type: (...) -> None

        if satisfaction_survey_code is not None and model is not None:
            report.append(APOS_DASH)
            ssc = satisfaction_survey_code
            satisfaction_survey_code = '-'.join(ssc[i:i + 3] for i in range(0, len(ssc), 3))
            report.append(self._get_text_line(text=satisfaction_survey_code))
            report.append(APOS_DASH)

    def _append_coupon_body(
        self,
        model,           # type: eTree.Element
        order,           # type: eTree.Element
        report,          # type: List[AposA8PrintLine]
        tenders,         # type: List[Any]
        printer_common,  # type: PrinterCommon
    ):
        # type: (...) -> None

        append_items_header(
            report=report,
            font_size=self.danfe_font_size_mobile,
        )

        report.append(APOS_DASH)

        items_amount = append_and_count_sale_items(
            report=report,
            order=order,
            printer_common=printer_common,
            font_size=self.danfe_font_size_mobile,
        )

        report.append(APOS_DASH)

        self._append_total(
            report=report,
            order=order,
            tenders=tenders,
            items_amount=items_amount,
        )

        report.append(APOS_DASH)

        if self.order_ref_position == "BETWEEN_FISCAL_DATA":
            build_call_id(
                model=model,
                report=report,
                order=order,
                font_size_withdrawal_panel=self.font_size_withdrawal_panel,
                printer_common=printer_common,
                order_ref_ids=self.order_ref_ids,
            )

        append_coupon_fiscal_parameters(
            coupon_fiscal_parameters=self.coupon_fiscal_parameters,
            report=report,
            order=order,
            font_size=self.danfe_font_size_mobile,
        )

        satisfaction_survey_code = self.get_satisfaction_survey_code(order=order)
        self._append_satisfaction_survey(
            report=report,
            model=model,
            satisfaction_survey_code=satisfaction_survey_code,
        )

    def _append_cliche(
        self,
        report,     # type: List[AposA8PrintLine]
    ):
        # type: (...) -> None

        for line in self.cliche_helper.get_cliche():
            split_lines(
                report=report,
                text=line,
                font_size=self.danfe_font_size_mobile,
            )

        report.append(APOS_DASH)

    def _append_danfe_key(
        self,
        report,     # type: List[AposA8PrintLine]
        key,        # type: str
    ):
        # type: (...) -> None

        sat_key = str(insert_space(key.strip(), 4))
        font_is_small = True if self.danfe_font_size_mobile == AposA8FontSize.SMALL else False

        lines = [sat_key[:39], sat_key[40:]] if font_is_small else [sat_key[:29], sat_key[30:]]
        append_lines_from_array(
            report=report,
            lines=lines,
            font_size=self.danfe_font_size_mobile,
        )

    def _get_text_line(
        self,
        text,           # type: str
        align=None,     # type: Optional[int]
    ):
        # type: (...) -> AposA8PrintLine

        align = align if align is not None else AposA8Align.CENTER

        return get_text_line(
            align=align,
            text=text,
            font_size=self.danfe_font_size_mobile,
        )

    def _get_column_width(
        self,
        subtract,   # type: int
    ):
        # type: (...) -> int

        is_small_font = self.danfe_font_size_mobile == AposA8FontSize.SMALL
        column_width_small = AposA8Constants.COLS - subtract
        column_width_large = AposA8Constants.COLS_FONT_LARGE - subtract
        return column_width_small if is_small_font else column_width_large
