# -*- coding: utf-8 -*-
import base64
import time
from datetime import datetime, timedelta

from common import PrinterCommon
from helper import remove_accents, encode_cp860
from xml.etree import cElementTree as eTree
from xml.etree.cElementTree import Element

import constants
import printer_wrapper
import sysactions
from clichehelper import ClicheHelper
from dateutil import tz
from msgbus import FM_PARAM, TK_SYS_ACK, TK_PRN_PRINT, MBException, MBEasyContext
from old_helper import F, get_sale_line_priced_items, regex_search, round_half_away_from_zero
from persistence import Driver as DBDriver, AprDbdException
from typing import (
    Optional,
    Dict,
    List,
    Union,
    Any,
)

SATISFACTION_SURVEY_TEXT = None  # type: Optional[str]
PAYMENTS_DESCRIPTION = {}


class FiscalPrinter(object):
    def __init__(
        self,
        mb_context,                     # type: MBEasyContext
        print_customer_name,            # type: bool
        cliche_helper,                  # type: ClicheHelper
        coupon_fiscal_parameters=None,  # type: Optional[Dict[str, bool]]
    ):
        # type: (...) -> None

        self.mb_context = mb_context
        self.print_customer_name = print_customer_name
        self.cliche_helper = cliche_helper
        sysactions.mbcontext = mb_context
        self.coupon_fiscal_parameters = coupon_fiscal_parameters or {}

    def print_fiscal(self, pos_id, order, tenders, fiscal_request, fiscal_response, total_taxes, customer_name, url_sefaz=""):
        raise NotImplementedError()

    def print_cancel(self, pos_id, data):
        raise NotImplementedError()

    @staticmethod
    def _center(s, cols=constants.COLS):
        if not s:
            return s

        s = SatFiscalPrinter._cut(s)
        miss = cols - len(s)

        if miss == 0:
            return s

        left = miss / 2

        return (" " * left) + s

    @staticmethod
    def _cut(s):
        if len(s) > constants.COLS:
            # If there is a line-feed at the end, keep it
            return s[:constants.COLS] if (s[-1] != "\n") else (s[:(constants.COLS - 1)] + "\n")

        return s

    @staticmethod
    def get_satisfaction_survey_code(order):
        global SATISFACTION_SURVEY_TEXT

        satisfaction_survey_property = order.find(
            "CustomOrderProperties/OrderProperty[@key='SATISFACTION_SURVEY_CODE']")
        if satisfaction_survey_property is not None:
            satisfaction_survey_code = satisfaction_survey_property.get("value")
            if SATISFACTION_SURVEY_TEXT is None:
                SATISFACTION_SURVEY_TEXT = sysactions.get_storewide_config("Store.GuestSatisfactionSurvey.Text")
        else:
            satisfaction_survey_code = None
        return satisfaction_survey_code

    @staticmethod
    def get_payment_description(mb_context, tender):
        if not PAYMENTS_DESCRIPTION:
            _build_payments_description_cache(mb_context)

        tender_id = str(tender.type)
        try:
            tender_descr = PAYMENTS_DESCRIPTION[tender_id].get("tender_description")
            parent_tender_id = PAYMENTS_DESCRIPTION[tender_id].get("parent_tender_id")
            if parent_tender_id:
                parent_tender_descr = PAYMENTS_DESCRIPTION[parent_tender_id].get("tender_description")
                tender_descr = "{} {}".format(parent_tender_descr, tender_descr)
        except KeyError as _:
            return "Outros"

        return tender_descr

    def _manager_lines_size(self, line, max_size=35):
        fields = []
        report = ""

        while line != '':
            current_line = line[0:max_size]
            fields.append(report + current_line)
            line = line[len(current_line):]

        return fields


class DonationCouponPrinter(object):
    def __init__(self, cliche, nf_type):
        # type: (ClicheHelper, str) -> None
        self.nf_type = nf_type
        self.cliche = cliche

    def print_coupon(self, pos_id, order_id, customer_name, donation_value, donation_institution, donation_cnpj, donation_site):
        model = sysactions.get_model(pos_id)
        report = ""

        report += constants.LINESPACING_FUNCS[1]
        report += constants.TXT_FONT_B
        report += constants.TXT_ALIGN_CT
        for row in self.cliche.get_cliche():
            report += row + "\n"
        report += constants.SEPARATOR_DASH + "\n"
        report += constants.TXT_ALIGN_LT
        report += "%-44s   %s" % (datetime.now().strftime("%d/%m/%y"), datetime.now().strftime("%H:%M:%S")) + "\n"
        report += "NAO E DOCUMENTO FISCAL\n"
        report += constants.TXT_ALIGN_CT + constants.TXT_BOLD_ON + "COMPROVANTE NAO FISCAL" + constants.TXT_BOLD_OFF + "\n"
        report += constants.TXT_ALIGN_LT
        client_name = constants.TXT_BOLD_ON \
                      + (customer_name[:30] or _translate(model, "UNINFORMED")).upper() \
                      + "\n" \
                      + constants.TXT_BOLD_OFF
        report += "%-12s   %46s" % (_translate(model, "ORDER_NUMBER", "%s" % order_id),
                                    _translate(model, "CLIENT_NAME", "%s" % remove_accents(client_name))) + "\n"
        report += "\n" + constants.SEPARATOR_DASH + "\n"

        return report


class SatFiscalPrinter(FiscalPrinter):
    def __init__(
        self,
        mb_context,                 # type: MBEasyContext
        print_customer_name,        # type: bool
        cliche_helper,              # type: ClicheHelper
        customer_text_field,        # type: str
        print_order_identifier,     # type: bool
        printer_common,             # type: PrinterCommon
        coupon_fiscal_parameters,   # type: Dict[str, bool]
    ):
        # type: (...) -> None

        super(SatFiscalPrinter, self).__init__(
            mb_context,
            print_customer_name,
            cliche_helper,
            coupon_fiscal_parameters,
        )

        self.customer_text_field = customer_text_field
        self.print_order_identifier = print_order_identifier
        self.printer_common = printer_common

    def print_fiscal(self, pos_id, order, tenders, fiscal_request, fiscal_response, total_taxes, customer_name, url_sefaz=""):
        # type: (str, Element, list, Element, Element, dict, str, Optional[str]) -> None

        xml = base64.b64decode(fiscal_request.text.strip())
        parsed_xml = eTree.XML(xml)
        n_c_fe = parsed_xml.find("infCFe/ide/nCFe").text
        model = sysactions.get_model(pos_id)

        totem_print = sysactions.get_podtype(sysactions.get_model(pos_id)) == "TT"
        customer_name = remove_accents(customer_name)
        report = self._build_report(order, tenders, fiscal_response, n_c_fe, customer_name, totem_print, model)
        barcode1, barcode2 = printer_wrapper.barcode(fiscal_response.find("satkey").text.strip())

        report += barcode1
        report += barcode2
        report += constants.CTL_LF
        qr_code_text = fiscal_response.find("QRCode").text.strip()
        size = 8 if qr_code_text == "FakeSATQRCode" else 3
        fields = self._build_fields_footer(fiscal_response, customer_name, order)

        qr_image = printer_wrapper.qr(qr_code_text, size=size)
        report += printer_wrapper.insert_text_on_right_image(qr_image, fields)

        report += constants.CTL_LF
        report = _build_coupon_tax(report, total_taxes)
        report += _rollback_printer_default_font()

        return report

    def print_cancel(
            self,
            pos_id,     # type: str
            data        # type: str
    ):
        # type: (...) -> None

        fiscal_response = eTree.XML(data)
        xml = base64.b64decode(
                fiscal_response.find("XmlResponse").text.strip()
        )

        parsed_xml = eTree.XML(xml)
        n_c_fe = parsed_xml.find("infCFe/ide/nCFe").text
        total = parsed_xml.find("infCFe/total/vCFe").text
        report = self._cancel_report(
                fiscal_response.find("FiscalData"),
                n_c_fe,
                total
        )

        barcode1, barcode2 = printer_wrapper.barcode(
                fiscal_response.find("FiscalData/satkey").text.strip()
        )

        qr_image = printer_wrapper.qr(fiscal_response.find("FiscalData/QRCode").text.strip())
        qr_code = printer_wrapper.insert_text_on_right_image(qr_image, [])

        report = report.encode("utf-8") + barcode1
        report += barcode2
        report += "\n"
        report += qr_code
        report += constants.TXT_ALIGN_LT

        for _ in range(0, 2):
            self._print_with_retry(
                    pos_id,
                    report
            )

    def _print_with_retry(self, pos_id, report):
        msg = None
        printer = "printer%s" % pos_id
        try:
            msg = self.mb_context.MB_EasySendMessage(printer, TK_PRN_PRINT, FM_PARAM, report, 10000000)
        except MBException:
            time.sleep(0.5)
            msg = self.mb_context.MB_EasySendMessage(printer, TK_PRN_PRINT, FM_PARAM, report, 10000000)
        finally:
            if msg and msg.token != TK_SYS_ACK:
                raise MBException("Erro imprimindo: %d", msg.token)

    def _cancel_report(self, sat_fiscal_response, ncfe_number, total):
        report = unicode("", "utf-8")
        report += constants.LINESPACING_FUNCS[1]
        report += constants.TXT_FONT_B
        report += constants.TXT_ALIGN_CT

        for line in self.cliche_helper.get_cliche():
            report += line + "\n"
        report += constants.SEPARATOR_DASH
        report += "\n"
        report += constants.TXT_BOLD_ON + "EXTRATO No. %s" % ncfe_number
        report += "\n"
        report += "CUPOM FISCAL ELETRONICO - SAT"
        report += "\nCANCELAMENTO"
        report += "\n" + constants.TXT_ALIGN_CT + constants.SEPARATOR_DASH

        report += "\nDADOS DO CUPOM FISCAL ELETRONICO CANCELADO"
        cpf_cnpj = sat_fiscal_response.find("CustomerCPF").text
        report += "\nCPF/CNPJ do Consumidor: %s" % ("Nao informado" if not cpf_cnpj else cpf_cnpj)
        report += "\nTOTAL R$ %s" % total
        report = self._build_footer(report, sat_fiscal_response)

        return report

    def _build_report(self, order, tenders, fiscal_request, ncfe_number, customer_name, totem_print=False, model=None):
        # type: (Element, list, Element, dict, str, Optional[list], Optional[Element]) -> unicode

        report = _build_start_configs(self.cliche_helper, totem_print)
        report = self._build_header(ncfe_number, report, fiscal_request, totem_print)
        report = _common_fiscal_coupon(
            self.mb_context,
            customer_name,
            model,
            order,
            report,
            tenders,
            totem_print,
            self.print_customer_name,
            self.print_order_identifier,
            self.customer_text_field,
            self.printer_common,
            self.coupon_fiscal_parameters,
        )
        report = self._build_sat_key(report, fiscal_request)
        return report

    def _build_sat_key(self, report, sat_fiscal_response):
        report += constants.TXT_ALIGN_CT
        from printer_wrapper import insert_space
        sat_key = insert_space(sat_fiscal_response.find("satkey").text.strip(), 4)
        report += self._center(str(sat_key))
        report += "\n"
        return report

    def _build_footer(self, report, sat_fiscal_response):
        report += "\n" + constants.SEPARATOR_DASH + "\n" + constants.TXT_ALIGN_CT
        report += constants.TXT_BOLD_ON + "SAT No. %s" % sat_fiscal_response.find("satno").text.strip() + constants.TXT_BOLD_OFF
        report += "\n"
        report += sat_fiscal_response.find("datetime").text.strip()
        report += "\n"
        report += constants.TXT_ALIGN_CT
        from printer_wrapper import insert_space
        sat_key = insert_space(sat_fiscal_response.find("satkey").text.strip(), 4)
        report += self._center(str(sat_key))
        report += "\n"
        return report

    def _build_fields_footer(self, fiscal_response, customer_name, order):
        fields = []

        document = fiscal_response.find("CustomerCPF").text
        report = "Consumidor: {}".format("Não informado" if not document else document)
        fields.append(report)

        if self.print_customer_name and order.find(".//OrderProperty[@key='TABLE_ID']") is None:
            if customer_name:
                content = "Nome: {}".format(customer_name.upper().strip())
            else:
                content = "Pedido: {}".format(order.get('orderId')[-3:].upper().strip())
            field = "%s" % content
            fields += self._manager_lines_size(field, 40)

        sat_number = "SAT nº %s" % fiscal_response.find("satno").text.strip()
        report = sat_number
        fields.append(report)

        report = fiscal_response.find("datetime").text.strip()
        fields.append(report)
        fields.append("Consulte o QRCode pelo Aplicativo")
        fields.append("de olho na nota disponível na")
        fields.append("AppStore (Apple) e Play Store (Android)")
        return fields

    @staticmethod
    def _build_header(ncfe_number, report, fiscal_response, totem_print):
        report += constants.TXT_ALIGN_CT + constants.SEPARATOR_DASH
        report += "\n"
        report += constants.TXT_BOLD_ON + "EXTRATO No. %s" % ncfe_number
        report += "\n"
        report += u"CUPOM FISCAL ELETRÔNICO - SAT".encode('cp860') + constants.TXT_BOLD_OFF
        report += "\n" + constants.SEPARATOR_DASH
        cpf_cnpj = fiscal_response.find("CustomerCPF").text
        report += "\nCPF/CNPJ do Consumidor: %s" % ("Nao informado" if not cpf_cnpj else cpf_cnpj)
        report += "\n" + constants.TXT_ALIGN_LT + constants.SEPARATOR_DASH + constants.CTL_LF
        report += "\nCOD     DESCR              QTD UN    VL UNIT       TOTAL"
        report += "\n"
        return report


class NfceFiscalPrinter(FiscalPrinter):
    def __init__(
        self,
        mb_context,                 # type: MBEasyContext
        print_customer_name,        # type: bool
        cliche_helper,              # type: ClicheHelper
        qrcode_check_url,           # type: str
        versao_ws,                  # type: int
        customer_text_field,        # type: str
        print_order_identifier,     # type: bool
        printer_common,             # type: PrinterCommon
        coupon_fiscal_parameters,   # type: Dict[str, bool]
    ):
        # type: (...) -> None
        super(NfceFiscalPrinter, self).__init__(
            mb_context,
            print_customer_name,
            cliche_helper,
            coupon_fiscal_parameters,
        )
        self.qrcode_check_url = qrcode_check_url
        self.versao_ws = versao_ws
        self.customer_text_field = customer_text_field
        self.print_order_identifier = print_order_identifier
        self.printer_common = printer_common

    def print_fiscal(self, pos_id, order, tenders, fiscal_request, fiscal_response, total_taxes, customer_name, url_sefaz=""):
        # type: (str, Element, list, Element, Element, dict, str, str) -> unicode

        model = sysactions.get_model(pos_id)

        re_search = r"<[A-z0-9]*?:?{0}>(.*?)</([A-z0-9]*?):?{0}>"
        tp_emiss = regex_search(re_search, eTree.tostring(fiscal_request), key="tpEmis")

        totem_print = sysactions.get_podtype(model) == "TT"
        customer_name = remove_accents(customer_name)
        if tp_emiss == "9":
            report = self._build_report(order, tenders, fiscal_request, fiscal_response, total_taxes, "Via Estabelecimento", customer_name, self.qrcode_check_url, totem_print, model)
            report += "\n\n\n\n\n\n\n\n"
            report += constants.PAPER_PART_CUT
            report += self._build_report(order, tenders, fiscal_request, fiscal_response, total_taxes, "Via Consumidor", customer_name, self.qrcode_check_url, totem_print, model)
            return report

        report = self._build_report(order, tenders, fiscal_request, fiscal_response, total_taxes, "", customer_name, self.qrcode_check_url, totem_print, model)
        return report

    def print_cancel(self, pos_id, data):
        return ""

    def _build_report(self, order, tenders, fiscal_request, fiscal_response, total_taxes, via, customer_name, url_sefaz, totem_print=False, model=None):
        # type: (Element, list, Element, Element, dict, str, str, str, Optional[bool], Optional[Element]) -> str

        report = _build_start_configs(self.cliche_helper, totem_print)
        re_search, report, tp_emiss = self._build_header(fiscal_request, report)
        report = _common_fiscal_coupon(
            mb_context=self.mb_context,
            customer_name=customer_name,
            model=model,
            order=order,
            report=report,
            tenders=tenders,
            totem_print=totem_print,
            print_customer_name=self.print_customer_name,
            print_order_identifier=self.print_order_identifier,
            customer_text_field=self.customer_text_field,
            printer_common=self.printer_common,
            coupon_attrs=self.coupon_fiscal_parameters,
        )

        report = self._build_key_access(report, url_sefaz, fiscal_request)
        report += self._build_footer(fiscal_request, fiscal_response, re_search, tp_emiss, via)
        report = _build_coupon_tax(report, total_taxes)
        report += _rollback_printer_default_font()

        return report

    @staticmethod
    def _build_key_access(report, url_sefaz, fiscal_request):
        report += constants.TXT_ALIGN_CT + constants.TXT_BOLD_ON
        report += "\nConsulte pela Chave de Acesso em\n"
        report += constants.TXT_BOLD_OFF
        report += url_sefaz + "\n"
        re_search_attribute_id = r"<[A-z0-9]*?:?infNFe.*?Id=\"(.*?)\""
        chave = regex_search(re_search_attribute_id, eTree.tostring(fiscal_request), key="infNFe")[3:]
        for i in range(0, 44, 4):
            report += chave[i:i + 4] + " "
        report += "\n"

        return report

    def _build_footer(self, fiscal_request, fiscal_response, re_search, tp_emiss, via):

        customer_properties_search = r"<dest>[\s\S]*?<{0}>(.*?)</{0}>[\s\S]*?</dest>"
        cpf_cnpj = ""
        cpf = regex_search(customer_properties_search, eTree.tostring(fiscal_request), key="CPF")
        if cpf is not None:
            cpf_cnpj = cpf
        else:
            cnpj = regex_search(customer_properties_search, eTree.tostring(fiscal_request), key="CNPJ")
            if cnpj is not None:
                cpf_cnpj = cnpj

        # Consumidor
        fields = []
        report = "CONSUMIDOR %s" % ("NÃO INFORMADO" if not cpf_cnpj else ("- CPF/CNPJ " + cpf_cnpj))
        fields.append(report)

        numero = regex_search(re_search, eTree.tostring(fiscal_request), key="nNF")
        serie = regex_search(re_search, eTree.tostring(fiscal_request), key="serie")
        data_emissao_xml = regex_search(re_search, eTree.tostring(fiscal_request), key="dhEmi")
        data_emissao_str = _format_date(data_emissao_xml)

        report = "NFC-e Número %09d" % (int(numero))
        fields.append(report)
        report = "Série {:03d}  {}".format(int(serie), data_emissao_str)
        fields.append(report)

        tp_amb = regex_search(re_search, eTree.tostring(fiscal_request), key="tpAmb")
        if tp_emiss == "9":
            lines = ClicheHelper.manager_lines_size(via, 33)
            for line in lines:
                fields.append(line)

        def handle_datetime_str(date_string):
            from dateutil.parser import parse
            parsed_utc_date_string = parse(date_string)
            return parsed_utc_date_string.strftime("%d/%m/%YT%H:%M:%S.%f")[:-7]

        if fiscal_response is not None:
            protocol_number = regex_search(re_search, eTree.tostring(fiscal_response), key="nProt")
            if protocol_number is not None:

                text_information = "Protocolo de autorização: {}".format(protocol_number)
                lines_protocol = ClicheHelper.manager_lines_size(text_information, 45)
                for line in self.manager_lines(lines_protocol):
                    fields.append(line)

                protocol_date = regex_search(re_search, eTree.tostring(fiscal_response), key="dhRecbto")
                text_information = "Data de autorização: {}".format(handle_datetime_str(protocol_date).replace('T', ' '))
                lines_date = ClicheHelper.manager_lines_size(text_information, 45)
                for line in self.manager_lines(lines_date):
                    fields.append(line)

        if tp_amb == "2":
            report = "Emitida em Homologação"
            fields.append(report)
            report = "Sem Valor Fiscal"
            fields.append(report)
        if tp_emiss == "9":
            report = "Emitida em Contingência"
            fields.append(report)
            report = "Pendente de Autorização"
            fields.append(report)

        report += constants.CTL_LF
        url = regex_search(re_search, eTree.tostring(fiscal_request), key="qrCode")

        if fiscal_response and regex_search(re_search, eTree.tostring(fiscal_response), key="cMsg"):
            message_code = regex_search(re_search, eTree.tostring(fiscal_response), key="cMsg")
            message = regex_search(re_search, eTree.tostring(fiscal_response), key="xMsg")
            if message_code == '200' and message not in [None, '', ' ']:
                report += '\n' + message.replace('|', '\n') + '\n'

        qr_image = printer_wrapper.qr(url.strip(), size=4)
        report += printer_wrapper.insert_text_on_right_image(qr_image, fields)

        return report

    def manager_lines(self, lines):
        # type: (List[str]) -> str

        report = ""
        for index, line in enumerate(lines):
            if index == 0:
                index_separator = line.find(":")
                new_line = line[:index_separator] + line[index_separator:]
                report += new_line
                yield report
                continue
            yield line



    @staticmethod
    def _build_header(fiscal_request, report):
        report += constants.SEPARATOR_DASH
        report += "\n"
        report += "Documento Auxiliar da Nota Fiscal\n"
        report += "de Consumidor Eletronica\n"
        re_search = r"<[A-z0-9]*?:?{0}>(.*?)</([A-z0-9]*?):?{0}>"
        tp_emiss = regex_search(re_search, eTree.tostring(fiscal_request), key="tpEmis")
        if tp_emiss == "9":
            report += constants.SEPARATOR_DASH
            report += constants.TXT_BOLD_ON
            report += "EMITIDA EM CONTINGENCIA\n"
            report += constants.TXT_BOLD_OFF
            report += "Pendente de autorizacao\n"
        report += constants.TXT_ALIGN_LT + constants.SEPARATOR_DASH
        report += constants.TXT_BOLD_ON
        report += "COD     DESCR              QTD UN    VL UNIT       TOTAL\n"
        report += constants.TXT_BOLD_OFF
        return re_search, report, tp_emiss


def _common_fiscal_coupon(
    mb_context,                 # type: MBEasyContext
    customer_name,              # type: str
    model,                      # type: Element
    order,                      # type: Element
    report,                     # type: str
    tenders,                    # type: List
    totem_print,                # type: bool
    print_customer_name,        # type: bool
    print_order_identifier,     # type: bool
    customer_text_field,        # type: str
    printer_common,             # type: PrinterCommon
    coupon_attrs,               # type: Dict[str, bool]
):
    # type: (...) -> str

    sale_lines = get_sale_line_priced_items(order.findall("SaleLine"))
    items_amount, order_tip, report = _build_sale_lines(order, report, sale_lines, printer_common)
    report = _build_order_totals(mb_context, items_amount, order, order_tip, report, tenders)
    report = _build_customer_name(print_customer_name, customer_name, order, report)
    report = _build_satisfaction_survey_code(model, report, order)
    report = _build_order_identifier(
        report=report,
        order=order,
        coupon_attrs=coupon_attrs,
    )
    report = _get_delivery_info(order, report)
    report = _build_custom_text_field(customer_text_field, report)
    return report


def _build_sale_lines(order, report, sale_lines, printer_common):
    items_amount = 0
    for line in sale_lines:
        line_params = map(line.get, ("itemPrice", "multipliedQty", "productName", "partCode", "level"))
        item_price, multiplied_qty, name, part_code, level = line_params
        measure_unit = line.get("measureUnit") if "measureUnit" in line.attrib else "un"
        items_amount += 1
        quantity = F(multiplied_qty)
        item_value = round_half_away_from_zero(F(item_price) / quantity, 2)
        line = "%-7.7s %-18.18s%6.3f%2.2s%9.02f" % (part_code, name, quantity, measure_unit, item_value)
        string = ("%-46.46s %9.02f\n" % (line, F(item_price)))

        report += string.encode('cp860')
    order_tip = 0.0
    if order.get("tip") and float(order.get("tip")) > 0:
        order_tip = F(order.get("tip"))
        tips_lines = printer_common.get_tip_order(order)
        report += tips_lines
        items_amount += len(tips_lines.split("\n")) - 1

    return items_amount, order_tip, report


def _build_satisfaction_survey_code(model, report, order):
    satisfaction_survey_code = FiscalPrinter.get_satisfaction_survey_code(order)
    if satisfaction_survey_code is not None and model is not None:
        report += constants.TXT_ALIGN_CT
        report += constants.TXT_NORMAL
        ssc = satisfaction_survey_code
        satisfaction_survey_code = ssc[:3] + '-' + ssc[3:6] + '-' + ssc[6:9] + '-' + ssc[9:12] + '-' + ssc[12:15] + '-' + ssc[-1]
        report += SATISFACTION_SURVEY_TEXT + satisfaction_survey_code + "\n"
        report += constants.TXT_FONT_B
        report += constants.SEPARATOR_DASH + "\n"
    return report


def _build_customer_name(print_customer_name, customer_name, order, report):
    if print_customer_name and order.find(".//OrderProperty[@key='TABLE_ID']") is None:
        char_amount = 21
        content = (customer_name or '')[:(char_amount - 2)] or order.get('orderId')[-3:]
        report += constants.TXT_ALIGN_CT
        report += constants.TXT_BOLD_ON + encode_cp860("PAINEL DE RETIRADA - VOCÊ SERA CHAMADO POR:") + constants.TXT_BOLD_OFF
        report += "\n"
        report += constants.TXT_FONT_A + constants.TXT_INVERT_ON + constants.TXT_ALIGN_CT
        report += constants.TXT_SIZE + chr(constants.TXT_WIDTH[2] + constants.TXT_HEIGHT[2])

        total_whitespace = char_amount - len(content)
        half_whitespace = total_whitespace // 2
        odd_division = total_whitespace % 2 != 0
        report += " " * half_whitespace
        report += "%s" % content.upper()
        report += " " * (half_whitespace + (1 if odd_division else 0))

        report += constants.TXT_SIZE + chr(constants.TXT_WIDTH[1] + constants.TXT_HEIGHT[1])
        report += constants.TXT_FONT_B + constants.TXT_INVERT_OFF
        report += "\n" + constants.SEPARATOR_DASH + "\n"

    return report


def _get_initial_line(
):
    # type: (...) -> unicode

    line = constants.LINESPACING_FUNCS[1]
    line += constants.TXT_FONT_B
    line += constants.INITIALIZE_LINE
    return line


def _build_start_configs(cliche_helper, totem_print):
    report = ""

    report += _get_initial_line()
    report += constants.CHARCODE_PC860
    report += constants.TOTEM_LINE_SPACING
    report += constants.TXT_FONT_B

    report += cliche_helper.get_header_coupon()
    report += constants.TXT_ALIGN_CT
    return report


def _build_custom_text_field(customer_text_field, report):
    if customer_text_field != "":
        report += constants.TXT_ALIGN_CT
        customer_name = customer_text_field.replace("\\n", "\n")
        report += "\n" + "%s" % encode_cp860(customer_name)
        report += "\n" + constants.SEPARATOR_DASH + "\n"
    return report


def _build_order_identifier(
    report,                     # type: str
    order,                      # type: eTree.XML
    coupon_attrs,               # type: Dict
):
    # type: (...) -> unicode

    order_info = _get_order_info(
        order=order,
        coupon_attrs=coupon_attrs,
    )
    if order_info:
        report += order_info
        report += constants.SEPARATOR_DASH + "\n"

    return report


def _get_delivery_info(order, report):
    delivery_info = _get_delivery_partner_and_short_reference(order)
    if delivery_info:
        report += delivery_info
        report += "\n" + constants.SEPARATOR_DASH + "\n"

    return report


def _get_order_info(
    order,          # type: Dict
    coupon_attrs,   # type: Dict
):
    # type: (...) -> unicode

    report = ""
    if coupon_attrs["operator"]:
        user_id = order.get("sessionId").split("user=")[1].split(",")[0]
        operator_name = eTree.XML(sysactions.get_user_information(user_id)).find(".//user").get("LongName")[:15]
        report += "Operador: {} \n".format(operator_name)

    if coupon_attrs["order_or_table"]:
        order_id = order.get("orderId")
        table_property = order.find("CustomOrderProperties/OrderProperty[@key='TABLE_NAME']")
        table_name = table_property.get("value") if table_property is not None else ''
        table_name = table_name[:15] + "..." if len(table_name) > 15 else table_name
        if table_name:
            report += "Pedido/Mesa: {}/{} \n".format(order_id[-4:], table_name)
        else:
            report += "Pedido: {} \n".format(order_id[-4:])

    if coupon_attrs["pos_id"]:
        pos_id = order.get("posId")
        report += "POS ID: {} \n".format(pos_id)

    return report


def _build_coupon_tax(report, total_taxes):
    report += constants.TXT_FONT_B
    report += constants.TXT_ALIGN_CT
    total_tax_amount = float(total_taxes['nacionalfederal']['value']) + float(total_taxes['estadual']['value'])
    report += "%-44s   R$%7.2f\n" % ("Valor aproximado dos tributos deste cupom", total_tax_amount)
    report += "Fed = R$%7.2f (%.2f%%), Est = R$%7.2f (%.2f%%)" % (total_taxes['nacionalfederal']['value'],
                                                                  total_taxes['nacionalfederal']['percent'],
                                                                  total_taxes['estadual']['value'],
                                                                  total_taxes['estadual']['percent'])
    report += "\n(Conforme Lei Fed. 12.741/2012)"
    return report


def _rollback_printer_default_font():
    report = "{}{}{}".format(constants.HW_INIT, constants.TXT_NORMAL, constants.TXT_BOLD_OFF)
    report += "{}{}".format(constants.TXT_FONT_A, constants.TXT_ALIGN_LT)

    return report


def _build_order_totals(mb_context, items_amount, order, order_tip, report, tenders):
    discount_amount = float(order.get("discountAmount") or 0)
    total_amount = float(order.get("totalAmount"))
    tax_total = float(order.get("taxTotal"))
    order_voided = order.get("state") == "VOIDED"
    factor = -1 if order_voided else 1

    report += constants.SEPARATOR_DASH + "\n"
    report += "%-44s         %3d\n" % ("QTD. TOTAL DE ITENS", items_amount)
    report += "%-44s   R$%7.2f\n" % ("VALOR TOTAL", total_amount + tax_total + order_tip)

    if discount_amount:
        bonus_type = "DESCONTO" if discount_amount > 0 else "ACRESCIMO"
        report += "%-44s   R$%7.2f\n" % (bonus_type, abs(discount_amount))

    total_amount = total_amount + tax_total - discount_amount + order_tip
    report += "%-44s   R$%7.2f\n" % ("VALOR A PAGAR", total_amount)
    report += "\n%-44s  %s\n" % ("FORMA DE PAGAMENTO", "Valor Pago")

    for tender in tenders:
        tender_name = FiscalPrinter.get_payment_description(mb_context, tender)
        tender_descr = encode_cp860(tender_name)
        tender_amount = tender.amount
        report += "%-44s   R$%7.2f\n" % (tender_descr, tender_amount)

    change = tenders[len(tenders) - 1].change  # Get "Change" (sixth value of tender tuple) from last tender in tenders
    if change > 0:
        report += "%-44s   R$%7.2f\n" % ("Troco", change * factor)

    report += constants.SEPARATOR_DASH + "\n"
    return report


def _build_payments_description_cache(mb_context):
    global PAYMENTS_DESCRIPTION

    conn = DBDriver().open(mb_context)
    has_parent_tender_id_on_db = True
    try:
        result = conn.select("SELECT TenderId, TenderDescr, ParentTenderId FROM TenderType")
    except AprDbdException:
        has_parent_tender_id_on_db = False
        result = conn.select("SELECT TenderId, TenderDescr FROM TenderType")
    for row in result:
        tender_id = row.get_entry("TenderId")
        tender_descr = row.get_entry("TenderDescr")
        parent_tender_id = row.get_entry("ParentTenderId") if has_parent_tender_id_on_db else None
        PAYMENTS_DESCRIPTION[tender_id] = {"tender_description": tender_descr, "parent_tender_id": parent_tender_id}


def _format_date(data, mascara="%d/%m/%Y %H:%M:%S"):
    data_emissao_date_utc = data[:19]
    data_emissao_date_utc = datetime.strptime(data_emissao_date_utc, "%Y-%m-%dT%H:%M:%S")
    data_emissao_offset = int(data[20:22]) * 60 + int(data[23:])
    if data[19] == "+":
        data_emissao_offset *= -1
    data_emissao_date_utc = data_emissao_date_utc + timedelta(minutes=data_emissao_offset)
    from_zone = tz.tzutc()
    to_zone = tz.tzlocal()

    data_emissao_date = data_emissao_date_utc.replace(tzinfo=from_zone)
    data_emissao_date = data_emissao_date.astimezone(to_zone)
    data_emissao_str = data_emissao_date.strftime(mascara)
    return data_emissao_str


def _translate(model, text, *param):
    return remove_accents(sysactions.translate_message(model, text, *param))


def _get_delivery_partner_and_short_reference(order):
    delivery_partner = order.find("CustomOrderProperties/OrderProperty[@key='PARTNER']")
    if delivery_partner is not None:
        short_reference = order.find("CustomOrderProperties/OrderProperty[@key='SHORT_REFERENCE']")
        if short_reference is not None:
            return "\n{}: {}".format(delivery_partner.get("value").upper(), short_reference.get("value").upper())

    return ""
