from xml.etree import cElementTree as eTree

from helper import remove_accents
from typing import (
    Dict,
    List,
    Optional,
    Any,
)

import sysactions

from helper import translate_pos_label
from old_helper import F, get_sale_line_priced_items, round_half_away_from_zero

from common import PrinterCommon
from printer import simplify_sale_line_elements
from _AposA8PrintLine import AposA8PrintLine
from _AposA8Align import AposA8Align
from _AposA8CommandType import AposA8CommandType
from _AposA8Constants import AposA8Constants
from _AposA8FontSize import AposA8FontSize
from services import order_xml_has_valid_fee

APOS_DASH = AposA8PrintLine(AposA8Align.CENTER, AposA8Constants.SEPARATOR_DASH, AposA8CommandType.PRINT_TEXT)
APOS_NEWLINE = AposA8PrintLine(AposA8Align.CENTER, "", AposA8CommandType.PRINT_TEXT)
APOS_HASH = AposA8PrintLine(AposA8Align.CENTER, AposA8Constants.SEPARATOR_HASH, AposA8CommandType.PRINT_TEXT)


def add_loyalty_custom_text(report, order, custom_text):
    # type: (list, eTree.Element, dict) -> None
    loyalty_user_name = order.find("CustomOrderProperties/OrderProperty[@key='CUSTOMER_NAME']")
    loyalty_user_cpf = order.find("CustomOrderProperties/OrderProperty[@key='LOYALTY_SYSTEM_USER_CPF']")

    if loyalty_user_name is not None and loyalty_user_cpf is not None:
        name_and_cpf = custom_text["name_cpf"].format(nome_cliente=loyalty_user_name.get("value"),
                                                      cpf_cliente=loyalty_user_cpf.get("value"))
        report.append(AposA8PrintLine(AposA8Align.CENTER, name_and_cpf, AposA8CommandType.PRINT_TEXT))
    elif loyalty_user_cpf is not None:
        cpf = custom_text["cpf"].format(cpf_cliente=loyalty_user_cpf.get("value"))
        report.append(AposA8PrintLine(AposA8Align.CENTER, cpf, AposA8CommandType.PRINT_TEXT))
    else:
        non_participating = custom_text["non_participating"]
        report.append(AposA8PrintLine(AposA8Align.CENTER, non_participating, AposA8CommandType.PRINT_TEXT))

    report.append(APOS_NEWLINE)
    report.append(APOS_DASH)
    report.append(APOS_NEWLINE)


def append_items_header(
    report,         # type: List[AposA8PrintLine]
    font_size,      # type: str
):
    # type: (...) -> None

    cod = _get_label_item(
        text='COD',
        column=0,
        font_size=font_size,
    )

    descr = _get_label_item(
        text='DESCR',
        column=1,
        font_size=font_size,
    )

    qtd = _get_label_item(
        text='QTD',
        column=2,
        font_size=font_size,
    )

    un = _get_label_item(
        text='UN',
        column=3,
        font_size=font_size,
    )

    vl_unit = _get_label_item(
        text='VL UNIT',
        column=4,
        font_size=font_size,
        align='>'

    )

    total = _get_label_item(
        text='TOTAL',
        column=5,
        font_size=font_size,
        align='>'

    )

    if font_size == AposA8FontSize.SMALL:
        header_lines = '{}{}{}{}{}{}'.format(cod, descr, qtd, un, vl_unit, total)
        report.append(
            get_text_line(
                text=header_lines,
                font_size=font_size,
            )
        )
        return

    header_lines = '{}{}'.format(cod, descr)
    report.append(
        get_text_line(
            text=header_lines,
            font_size=font_size,
            align=AposA8Align.LEFT,
        )
    )

    vl_unit = format_text(
        text=vl_unit.strip(),
        width=10,
        align='>',
    )

    total = format_text(
        text=total.strip(),
        width=13,
        align='>',
    )

    header_lines = '{}{}{}{}'.format(qtd, un, vl_unit, total)
    report.append(
        get_text_line(
            text=header_lines,
            font_size=font_size,
            align=AposA8Align.LEFT,
        )
    )


def _get_label_item(
    text,           # type: str
    column,         # type: int
    font_size,      # type: str
    align='<',      # type: str
):
    # type: (...) -> str

    get_columns_size = _get_columns_size(
        column=column,
        font_size=font_size,
    )

    return format_text(
        text=text[:get_columns_size],
        align=align,
        width=get_columns_size,
    )


def format_text(
    text,           # type: str
    width,          # type: int
    align='<',      # type: str
):
    # type: (...) -> str

    text_len = len(remove_accents(text=text))

    if align == '<':
        text += (" " * (width - text_len))
    else:
        text = (" " * (width - text_len)) + text

    return text


def _get_columns_size(
    column,         # type: int
    font_size,      # type: str
):
    # type: (...) -> int

    columns_and_size = {
        0: 8,
        1: 14,
        2: 7,
        3: 2,
        4: 8,
        5: 9,
    }

    if font_size != AposA8FontSize.SMALL:
        columns_and_size = {
            0: 8,
            1: AposA8Constants.COLS_FONT_LARGE - 8,
            2: 6,
            3: 3,
            4: 18,
            5: 18,
        }

    return columns_and_size[column]


def append_and_count_sale_items(
    report,             # type: List[AposA8PrintLine]
    order,              # type: eTree.Element
    printer_common,     # type: PrinterCommon
    font_size,          # type: str
):
    # type: (...) -> int

    items_amount = 0

    sale_lines = simplify_sale_line_elements(order.findall("SaleLine"))
    sale_lines = get_sale_line_priced_items(sale_lines)

    for line in sale_lines:
        line_params = _get_line_params(line)
        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)
        if order_xml_has_valid_fee(order_xml=order):
            fee_amount = float(line.get("feeIncludedInPrice") or 0.0)
            item_price = round_half_away_from_zero(F(item_price) - fee_amount, 2)
        item_value = round_half_away_from_zero(F(item_price) / quantity, 2)
        _append_sale_item(
            report=report,
            part_code=part_code,
            name=name,
            quantity=quantity,
            measure_unit=measure_unit,
            item_value=item_value,
            item_price=item_price,
            font_size=font_size,
        )

    if float(order.get("tip", 0)) > 0:
        tips_lines = printer_common.get_tip_order(order, is_mobile=True)
        tips_lines = tips_lines.split("\n")
        for line in tips_lines:
            split_line = line.split("|")
            if not split_line[0]:
                continue

            items_amount += 1
            _append_tip(
                report=report,
                split_line=split_line,
                font_size=font_size,
            )

    return items_amount


def _append_tip(
    report,         # type: List[AposA8PrintLine]
    split_line,     # type: List[str]
    font_size,      # type: str
):
    # type: (...) -> None

    part_code = split_line[0].strip()
    name = split_line[1].strip()
    quantity = float(split_line[2].strip() or '0.00')
    measure_unit = split_line[3].strip()
    item_value = float(split_line[4].strip() or '0.00')
    item_price = float(split_line[5].strip() or '0.00')

    _append_sale_item(
        report=report,
        part_code=part_code,
        name=name,
        quantity=quantity,
        measure_unit=measure_unit,
        item_value=item_value,
        item_price=item_price,
        font_size=font_size,
    )


def _append_sale_item(
    report,         # type: List[AposA8PrintLine]
    part_code,      # type: str
    name,           # type: str
    quantity,       # type: float
    measure_unit,   # type: str
    item_value,     # type: float
    item_price,     # type: float
    font_size,      # type: str
):

    part_code = _get_label_item(
        text=part_code,
        column=0,
        font_size=font_size,
    )

    name = _get_label_item(
        text=name,
        column=1,
        font_size=font_size,
    ).encode("utf-8")

    quantity = _get_label_item(
        text="{:.3f}".format(quantity),
        column=2,
        font_size=font_size
    )

    measure_unit = _get_label_item(
        text=measure_unit.upper(),
        column=3,
        font_size=font_size,
    )

    item_value = _get_label_item(
        text="{:.2f}".format(item_value),
        column=4,
        font_size=font_size,
        align='>'
    )

    item_price = _get_label_item(
        text="{:.2f}".format(F(item_price)),
        column=5,
        font_size=font_size,
        align='>'
    )

    if font_size == AposA8FontSize.SMALL:
        _append_item_small_font(
            report=report,
            font_size=font_size,
            part_code=part_code,
            name=name,
            quantity=quantity,
            measure_unit=measure_unit,
            item_price=item_price,
            item_value=item_value,
        )
    else:
        _append_item_large_font(
            report=report,
            font_size=font_size,
            part_code=part_code,
            name=name,
            quantity=quantity,
            measure_unit=measure_unit,
            item_price=item_price,
            item_value=item_value,
        )


def _append_item_small_font(
    report,             # type: List[AposA8PrintLine]
    font_size,          # type: str
    part_code,          # type: str
    name,               # type: bytes
    quantity,           # type: str
    measure_unit,       # type: str
    item_value,         # type: str
    item_price,         # type: str
):
    # type: (...) -> None

    item_line = '{}{}{}{}{}{}'.format(part_code, name, quantity, measure_unit, item_value, item_price)
    report.append(
        get_text_line(
            text=item_line,
            font_size=font_size,
        )
    )


def _append_item_large_font(
    report,             # type: List[AposA8PrintLine]
    font_size,          # type: str
    part_code,          # type: str
    name,               # type: bytes
    quantity,           # type: str
    measure_unit,       # type: str
    item_value,         # type: str
    item_price,         # type: str
):
    # type: (...) -> None

    item_line = '{}{}'.format(part_code, name[:24])
    report.append(
        get_text_line(
            text=item_line,
            font_size=font_size,
            align=AposA8Align.LEFT,
        )
    )

    quantity = format_text(
        text=quantity.rstrip(),
        width=7,
        align='>'
    )

    measure_unit = format_text(
        text=measure_unit,
        width=0,
    )

    item_value = format_text(
        text=item_value.strip(),
        width=9,
        align='>',
    )

    item_price = format_text(
        text=item_price.strip(),
        width=13,
        align='>',
    )

    item_line = '{}{}{}{}'.format(quantity, measure_unit, item_value, item_price)
    report.append(
        get_text_line(
            text=item_line,
            font_size=font_size,
            align=AposA8Align.LEFT,
        )
    )

    report.append(APOS_NEWLINE)


def _get_line_params(
        line    # type: eTree
):
    # type: (...) -> map

    return map(
                line.get,
                (
                    "itemPrice",
                    "multipliedQty",
                    "productName",
                    "partCode",
                    "level",
                )
        )


def append_tax_data(
    report,             # type: List[AposA8PrintLine]
    total_taxes,        # type: Dict
    font_size,          # type: str
):
    # type: (...) -> None

    tax_amount = float(total_taxes['nacionalfederal']['value']) + float(total_taxes['estadual']['value'])
    tax_text = "%-20s %7.2f" % ("Valor aproximado dos tributos deste cupom", tax_amount)
    split_lines(
        report=report,
        text=tax_text,
        font_size=font_size,
    )

    tax_federal_amount = "R$%7.2f" % total_taxes['nacionalfederal']['value']
    tax_federal_percentage = "(%.2f%%)" % total_taxes['nacionalfederal']['percent']
    federal_taxes = "Fed = {} {}".format(tax_federal_amount, tax_federal_percentage)

    tax_state_amount = "R$%7.2f" % total_taxes['estadual']['value']
    tax_state_percentage = "(%.2f%%)" % total_taxes['estadual']['percent']
    state_taxes = "Est = {} {}".format(tax_state_amount, tax_state_percentage)

    taxes = ["{}, {}".format(federal_taxes, state_taxes)]

    if font_size != AposA8FontSize.SMALL:
        taxes = [federal_taxes, state_taxes]

    for tax in taxes:
        report.append(
            get_text_line(
                text=tax,
                font_size=font_size,
            )
        )

    text = "(Conforme Lei Fed. 12.741/2012)"
    report.append(
        get_text_line(
            text=text,
            font_size=font_size,
        )
    )


def append_coupon_fiscal_parameters(
    coupon_fiscal_parameters,   # type: Dict[str, bool]
    report,                     # type: List
    order,                      # type: eTree.Element
    font_size,                  # type: str
):
    # type: (...) -> None

    if not any(coupon_fiscal_parameters.values()):
        return

    coupon_fiscal_text = []
    if coupon_fiscal_parameters["operator"]:
        user_id = order.get("sessionId").split("user=")[1].split(",")[0]
        coupon_fiscal_text.append("Operador: {}".format(user_id))

    if coupon_fiscal_parameters["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:
            coupon_fiscal_text.append("Pedido/Mesa: {}/{}".format(order_id[-4:], table_name))
        else:
            coupon_fiscal_text.append("Pedido: {}".format(order_id[-4:]))

    if coupon_fiscal_parameters["pos_id"]:
        pos_id = order.get("posId")
        model = sysactions.get_model(posid=pos_id)
        pos = translate_pos_label(model=model)
        coupon_fiscal_text.append("{} ID: {}".format(pos, pos_id))

    if len(coupon_fiscal_text) > 0:
        coupon_fiscal_text = " / ".join(coupon_fiscal_text) + "\n"
        split_lines(
            report=report,
            text=coupon_fiscal_text,
            font_size=font_size,
        )


def build_call_id(
    model,                          # type: eTree
    report,                         # type: List
    order,                          # type: eTree
    font_size_withdrawal_panel,     # type: str
    printer_common,                 # type: PrinterCommon
    order_ref_ids,                  # type: list
    position="between",             # type: Optional[str]
):
    # type: (...) -> None

    content = printer_common.get_coupon_call_id(
        order=order,
        order_ref_ids=order_ref_ids,
        customer_name_limit=21,
    )
    if not content:
        return

    is_table_order = order.find(".//OrderProperty[@key='TABLE_ID']") is not None
    if is_table_order:
        return

    if position == "between":
        report.append(APOS_HASH)

    panel_call_text = printer_common.get_coupon_call_text(
        model=model,
    )
    panel_call_text = panel_call_text.decode("cp860").encode("utf-8")

    split_lines(
        report=report,
        text=panel_call_text,
        font_size=font_size_withdrawal_panel,
        bold=True,
    )

    report.append(
        AposA8PrintLine(
            align=AposA8Align.CENTER,
            text=content.upper(),
            command_type=AposA8CommandType.PRINT_TEXT,
            font_size=font_size_withdrawal_panel,
            bold=True,
        )
    )

    report.append(APOS_HASH)

    if position == "before":
        report.append(APOS_NEWLINE)


def append_lines_from_array(
    report,         # type: List[AposA8PrintLine]
    lines,          # type: Optional[Any]
    font_size,      # type: str
    bold=False,     # type: Optional[bool]
):
    # type: (...) -> None

    for line in lines:
        report.append(
            get_text_line(
                text=line,
                font_size=font_size,
                bold=bold,
            )
        )


def split_lines(
    report,             # type: List[AposA8PrintLine]
    text,               # type: str
    font_size,          # type: str
    bold=False,         # type: Optional[bool]
    separator=" ",      # type: Optional[str]
):
    # type: (...) -> None

    lines = [text]
    is_small_font = font_size == AposA8FontSize.SMALL
    max_line_size = AposA8Constants.COLS if is_small_font else AposA8Constants.COLS_FONT_LARGE

    if max_line_size is not None and len(text) > max_line_size:
        lines = []
        text_list = text.split(separator)
        new_text = ""
        for index, line in enumerate(text_list):
            current_separator = "" if index == 0 else separator
            formatted_line = "{}{}{}".format(new_text, current_separator, line).strip()
            if len(formatted_line) <= max_line_size:
                new_text = formatted_line
                if index == len(text_list) - 1:
                    lines.append(new_text)
            else:
                lines.append(new_text)
                new_text = line.strip()
                if index == len(text_list) - 1:
                    lines.append(new_text)

    for line in lines:
        report.append(
            get_text_line(
                text=line,
                font_size=font_size,
                bold=bold,
            )
        )


def get_text_line(
    text,           # type: str
    font_size,      # type: str
    align=None,     # type: Optional[int]
    bold=False,     # type: Optional[bool]
):
    # type: (...) -> AposA8PrintLine

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

    return AposA8PrintLine(
        align=align,
        text=text,
        command_type=AposA8CommandType.PRINT_TEXT,
        font_size=font_size,
        bold=bold,
    )
