# -*- coding: utf-8 -*-
import collections
import time
import xml.sax
import xml.sax.handler
from cStringIO import StringIO
from xml.etree import ElementTree as eTree
import cfgtools
import os

# START - DO NOT CHANGE THIS IMPORT ORDINATION
import constants
import sysactions
from helper import MwOrderStatus, remove_accents
from production.processor import SystemOrderParser, OrderXml
from reports import mbcontext
import printer_wrapper
from constants import *
from systools import sys_log_exception, sys_log_warning, sys_parsefilepath

from picklistreport.repository import ProductRepository
# END - DO NOT CHANGE THIS IMPORT ORDINATION

sysactions.mbcontext = mbcontext

COLS = 42
SMALL_COLS = 42
SIMPLE_SEPARATOR = ("-" * SMALL_COLS)
SEPARATOR = ("=" * COLS)
SMALL_SEPARATOR = ("=" * SMALL_COLS)
DATE_TIME_FMT = "%d/%m/%Y %H:%M:%S"
DATE_FMT = "%d/%m/%Y"
TIME_FMT = "%H:%M:%S"
PICKUP_TYPE = None
CUSTOMER_TEXT = "\nCliente.......: {}"

config = cfgtools.read(os.environ["LOADERCFG"])
delivery_address = sys_parsefilepath(config.find_value("Picklist.DeliveryAddress") or "true").lower() == "true"
picklist_font_size_items = None


def production_pick_list(order_xml, *args):
    try:
        picklist_name = args[0] if len(args) > 0 else ""

        production_order, is_reprint = _parse_order_xml_to_production_order(order_xml)
        if _order_is_voided(production_order):
            sys_log_warning("ignoring voided order")
            return Exception()

        pod_type = production_order.get("pod_type")
        if pod_type == "KK":
            return Exception()

        report = StringIO()

        report.write("%(TXT_ALIGN_LT)s" % _join(globals(), locals()))
        report.write("%(TXT_BOLD_ON)s" % _join(globals(), locals()))

        report_header = _get_report_header(production_order, is_reprint, picklist_name)
        report.write(report_header)

        report.write("%(TXT_BOLD_OFF)s" % _join(globals(), locals()))

        pre_sale_lines_report = report.tell()

        formatted_sale_lines = _get_formatted_sale_lines_by_seats(production_order)

        report.write(formatted_sale_lines)

        if pre_sale_lines_report == report.tell():
            sys_log_warning("Order has no sale lines to print")
            return Exception()

        report = _add_additional_comments(report, production_order)
        report = _add_sale_type(report, production_order)

        qr_code = _get_qr_code(production_order)
        if qr_code:
            report.write(qr_code)

        return report.getvalue()
    except Exception as _:
        sys_log_exception("Error building production PickList")


def _order_is_voided(production_order):
    return MwOrderStatus.VOIDED.name == production_order.get("state", "")


def _get_qr_code(production_order):
    global PICKUP_TYPE
    try:
        if PICKUP_TYPE is None:
            PICKUP_TYPE = sysactions.get_storewide_config("Store.pickUpDisplayType", defval="Manager")
    except Exception as _:
        PICKUP_TYPE = 'Manager'

    # Prints QRCode only when it will be used for PickupDisplay on PickList
    if "PickList" in PICKUP_TYPE:
        return _create_qrcode('QR' + (production_order.get("order_id")).zfill(8) + 'BK')
    else:
        return ""


def _parse_order_xml_to_production_order(order_xml):
    order_xml = eTree.XML(order_xml)
    if order_xml.tag == "Orders":
        order = order_xml.find('Order')
        product_repository = ProductRepository(mbcontext)
        part_code_jit_lines = product_repository.get_all_part_code_jit_lines()
        handler = SystemOrderParser(part_code_jit_lines)
        xml.sax.parseString(eTree.tostring(order), handler)
        order_xml = handler.order
        convert_xml = OrderXml()
        order_xml = eTree.XML(convert_xml.to_xml(order_xml))
    production_order = order_xml if order_xml.tag == "ProductionOrder" else order_xml.find("ProductionOrder")

    is_reprint = False
    production_items = production_order.findall(".//Items") or []
    for item in production_items:
        is_reprint = len(item.findall(".//TagHistory/TagEvent[@tag='printed']")) > 0

    return production_order, is_reprint


def _get_report_header(production_order, is_reprint, picklist_name):
    header_pod_type = production_order.get("pod_type")
    header_sale_type = production_order.get("sale_type")
    totem_print = header_pod_type == "TT" or header_sale_type == "TOTEM"
    drive_print = header_pod_type == "DT" or header_sale_type == "DRIVE_THRU"
    delivery_print = header_pod_type == "DL" or header_sale_type == "DELIVERY"
    sale_type_text, sale_type = _get_sale_type(delivery_print, drive_print, production_order, totem_print)

    title = _center(picklist_name + sale_type_text, SMALL_COLS)

    produced_datetime = ""
    timestamp = _get_first_printed_time(production_order)
    if timestamp is not None:
        timestamp = time.strptime(timestamp[:19], "%Y-%m-%dT%H:%M:%S")
        produced_datetime = time.strftime(DATE_TIME_FMT, timestamp)

    posid = int(production_order.get("originator")[-2:])
    order_id = production_order.get("order_id")
    if order_id is not None:
        order_id = order_id.split("-")[0]
    user_id = production_order.get("session_id").split("user=")[1].split(",")[0]
    user_name = eTree.XML(sysactions.get_user_information(user_id)).find(".//user").get("LongName")[:15]
    user_info = "{} / {}".format(user_id, user_name)

    order_id = int(order_id) % 10000
    table_name = None
    if production_order.find(".//Property[@key='TAB_ID']") is not None:
        order_descr = "Comanda"
        table_name = production_order.find(".//Property[@key='TAB_ID']").get('value')
        table_name = table_name[:5] + "..." if len(table_name) > 5 else table_name
        order_descr_id = "{}".format(table_name)
    elif production_order.find(".//Property[@key='TABLE_NAME']") is not None:
        order_descr = "Mesa"
        table_name = production_order.find(".//Property[@key='TABLE_NAME']").get('value')
        table_name = table_name[:6] + "..." if len(table_name) > 6 else table_name
        order_descr_id = "{}".format(table_name)
    else:
        order_descr = "Pedido"
        order_descr_id = "{}".format(order_id)

    created_at = time.strptime(production_order.get("created_at"), "%Y-%m-%dT%H:%M:%S.%f")
    created_at = time.strftime(DATE_TIME_FMT, created_at)

    preheader = ""
    if is_reprint:
        preheader = "*** REIMPRESSO ***\n"
    preheader = _center(preheader, SMALL_COLS)

    header = constants.TXT_SIZE + chr(constants.TXT_WIDTH[2] + constants.TXT_HEIGHT[2])
    header += _center("%(order_descr)s: %(order_descr_id)s" % _join(globals(), locals()), COLS/2)
    header += "%(TXT_NORMAL)s%(TXT_BOLD_OFF)s%(TXT_FONT_A)s\n" % _join(globals(), locals())
    header += "%(preheader)s%(SMALL_SEPARATOR)s\n%(title)s" % _join(globals(), locals())
    header += "\nCodigo........: %(order_id)s (POS #%(posid)02d)" % _join(globals(), locals())
    header += _print_delivery_partner(delivery_print, production_order)

    customer_name = _get_xml_node_value(production_order, "CUSTOMER_NAME")
    if customer_name:
        header += CUSTOMER_TEXT.format(customer_name)

    current_datetime = time.strftime(DATE_TIME_FMT, time.localtime())
    if produced_datetime:
        header += "\nHora Pedido...: {}".format(created_at)
        header += "\nHora Produzido: {}".format(produced_datetime)
    else:
        header += "\nImpresso em...: {}".format(current_datetime)

    if is_reprint:
        header += "\nReimpresso....: %s" % current_datetime

    header += "\nOperador......: {}".format(user_info)
    header += "\n{}".format(SMALL_SEPARATOR)

    header += _print_delivery_info(delivery_print, production_order, sale_type)
    return header


def _print_delivery_partner(delivery_print, production_order):
    text = ""
    partner_node = _get_xml_node_value(production_order, "PARTNER")
    if delivery_print and partner_node:
        partner = partner_node.get("value", "") if hasattr(partner_node, 'get') else partner_node

        partner_id = ""
        partner_id_node = _get_xml_node_value(production_order, "SHORT_REFERENCE")
        if partner_id_node is not None:
            partner_id = partner_id_node.get("value") if hasattr(partner_id_node, 'get') else partner_id_node

        if partner != "" and partner_id != "":
            text += """\nParceiro......: {0}\nID Parceiro...: {1}""".format(partner.upper(), partner_id)

    return text


def _print_delivery_info(delivery_print, production_order, sale_type):
    text = ""
    valid_sale_type = sale_type is not None and sale_type.upper() not in ["TAKE_OUT", "EAT_IN"]
    if not delivery_print or not delivery_address or not valid_sale_type:
        return text

    try:
        street_name_element = _get_xml_node_value(production_order, "STREET_NAME")
        street_number_element = _get_xml_node_value(production_order, "STREET_NUMBER")
        if street_name_element:
            text += """\nEndereco....: {0}, {1}\n""".format(street_name_element, street_number_element)

        text += get_node_value(production_order, "COMPLEMENT", "Complemento.")
        text += get_node_value(production_order, "REFERENCE", "Referencia..")
        text += get_node_value(production_order, "NEIGHBORHOOD", "Bairro......")
        text += get_node_value(production_order, "POSTAL_CODE", "CEP.........")
        text += get_node_value(production_order, "CITY", "Cidade......")
        text += get_node_value(production_order, "STATE", "Estado......")
        if text != "":
            text += SMALL_SEPARATOR

    except Exception:
        sys_log_exception("Error in get address on Delivery picklist")

    return text


def get_node_value(production_order, node_value, text):
    complement_element = _get_xml_node_value(production_order, node_value)
    if complement_element:
        return """{}: {}\n""".format(text, complement_element)
    return ""


def _get_sale_type(delivery_print, drive_print, production_order, totem_print):
    sale_type_node = ""

    if totem_print:
        return "(TOTEM)", sale_type_node
    if drive_print:
        return "(DRIVE-THRU)", sale_type_node
    if delivery_print:
        sale_type, sale_type_node = _get_sale_type_by_pickup_type(production_order)
        return sale_type, sale_type_node

    sale_type, sale_type_node = _get_by_sale_type(production_order)

    return sale_type, sale_type_node


def _get_sale_type_by_pickup_type(production_order):
    sale_type = " (DELIVERY)"

    sale_type_node = _get_xml_node_value(production_order, "PICKUP_TYPE")
    if sale_type_node is not None:
        sale_type_node = sale_type_node.get("value", "") if hasattr(sale_type_node, 'get') else sale_type_node
        if sale_type_node.upper() == "TAKE_OUT":
            sale_type = " (DELIVERY - RETIRADA VIAGEM)"
        elif sale_type_node.upper() == "EAT_IN":
            sale_type = " (DELIVERY - RETIRADA LOJA)"

    return sale_type, sale_type_node


def _get_by_sale_type(production_order):
    sale_type_node = _get_xml_node_value(production_order, "SALE_TYPE")
    sale_type_node = sale_type_node.get("value", "") if hasattr(sale_type_node, 'get') else sale_type_node
    if sale_type_node is not None:
        if sale_type_node == "TAKE_OUT":
            sale_type = " (VIAGEM)"
        else:
            sale_type = " (LOJA)"
    else:
        sale_type = " (LOJA)"
        sale_type_node = ""

    return sale_type, sale_type_node


def _print_customer_name(production_order):
    # type: (eTree.XML) -> str

    customer_name = _get_xml_node_value(production_order, "CUSTOMER_NAME")
    partner_name = _get_xml_node_value(production_order, "PARTNER")
    short_reference = _get_xml_node_value(production_order, "SHORT_REFERENCE")

    if partner_name and short_reference:
        return CUSTOMER_TEXT.format(partner_name.upper() + " - " + short_reference)
    elif customer_name:
        return CUSTOMER_TEXT.format(customer_name.upper())

    return ""


def _get_grouped_sale_lines_by_seat(production_order):
    pos_id = int(production_order.get("originator")[-2:])
    model = sysactions.get_model(pos_id)

    grouped_sale_lines = {}
    pre_option_level = None
    preceding_removed_combo = False

    _fix_sub_items_seat(production_order)
    for item in production_order.findall(".//Item"):
        sale_line, pre_option_level, preceding_removed_combo = _get_formatted_sale_line(
                model,
                item,
                pre_option_level,
                preceding_removed_combo,
        )
        if sale_line:
            item_seat = item.get("seat") or 0
            if item_seat in grouped_sale_lines:
                grouped_sale_lines[item_seat].append(sale_line)
            else:
                grouped_sale_lines[item_seat] = [sale_line]

    return grouped_sale_lines.items() if len(grouped_sale_lines) > 0 else grouped_sale_lines


def _fix_sub_items_seat(production_order):
    items = production_order.findall("Items/Item")
    for item in items:
        item_sons = item.findall(".//Item")
        for son in item_sons:
            son.set("seat", item.get("seat"))


def _get_formatted_sale_lines_by_seats(production_order):

    formatted_sale_lines = "\n"
    formatted_sale_lines += _get_picklist_font_size_items()
    grouped_sale_lines = _get_grouped_sale_lines_by_seat(production_order)
    for seat, sale_lines_by_seat in collections.OrderedDict(sorted(grouped_sale_lines)).items():
        if production_order.find("./Properties/Property[@key='TABLE_ID']") is not None:
            formatted_sale_lines += _get_title_sale_lines_seats(seat, production_order)

        for sale_line in sale_lines_by_seat:
            if sale_line is not None:
                formatted_sale_lines += sale_line

        formatted_sale_lines += "\n"

    formatted_sale_lines += _rollback_printer_default_font()
    return formatted_sale_lines


def _get_title_sale_lines_seats(seat, production_order):
    # type: (str, eTree) -> str

    seat_title = " Assento {}"
    table_title = " Mesa"

    if int(seat) != 0:
        title = seat_title.format(seat)
    else:
        seats_number = production_order.find("./Properties/Property[@key='TABLE_SEATS']")
        seat_detail = " ({} assentos)".format(seats_number.get("value")) if seats_number is not None else ""
        title = table_title + seat_detail

    return "{}\n{}\n".format(title, SIMPLE_SEPARATOR)


def _get_formatted_sale_line(model, sale_line, pre_option_level=None, preceding_removed_combo=False, cols=COLS):
    item_type = None if "item_type" not in sale_line.attrib else sale_line.get("item_type")

    level = int(sale_line.get("level"))

    if item_type == "COMBO":
        preceding_removed_combo = False

    if level == 0:
        pre_option_level = None

    if item_type in [None, "OPTION", "COUPON"]:
        pre_option_level = level if item_type == "OPTION" else None
        return None, pre_option_level, preceding_removed_combo

    qty = float(sale_line.get("multiplied_qty") or sale_line.get("qty") or 0)
    default_qty = float(sale_line.get("default_qty") or 0)

    if (qty == 0 and default_qty == 0) or preceding_removed_combo:
        if item_type == "COMBO":
            preceding_removed_combo = True
        return None, None, preceding_removed_combo

    if qty < default_qty and qty == 0:
        qty_description = sysactions.translate_message(model, "WITHOUT")
    else:
        measure_unit = (sale_line.attrib.get("measure_unit", "UN")).upper()
        decimal_places = 3 if measure_unit == "KG" else 0
        quantity_format = "{" + ":.{}f".format(decimal_places) + "}"
        qty_description = quantity_format.format(qty)

    indentation = " " * ((pre_option_level or level) + 1)
    product_name = sale_line.get("description")

    manual_course = sale_line.get("manual_course")
    if manual_course and level == 0:
        manual_course = "[" + manual_course + "]"
    description_length = cols - len(manual_course)

    item_comment = sale_line.find("./Comment")
    if item_comment is not None:
        comment = item_comment.get("comment")

        comment_description = ""
        jumped_comment = ""
        if comment:
            if comment.startswith("["):
                comment_description = comment.replace("[", "").replace("]", "").upper()
                comment_description = sysactions.translate_message(model, comment_description)
            else:
                description = "[" + comment + "]"
                line_rjust = int(len(description) + len(indentation) + round(qty, 1))
                jumped_comment = remove_accents(description.rjust(line_rjust))

        product_description = str(comment_description or qty_description) + " " + product_name
        line_rjust = len(product_description) + len(indentation)

        product_description = product_description.rjust(line_rjust)
        if manual_course:
            product_description = product_description.ljust(description_length)[:description_length]

        description = remove_accents(product_description + manual_course)

        formatted_sale_line = description + "\n"
        if jumped_comment:
            formatted_sale_line += jumped_comment + "\n"
    else:
        product_description = indentation + qty_description + " " + product_name
        if manual_course:
            product_description = product_description.ljust(description_length)[:description_length]
        formatted_sale_line = remove_accents(product_description + manual_course) + "\n"

    if level <= pre_option_level:
        pre_option_level = None

    return formatted_sale_line, pre_option_level, preceding_removed_combo


def _get_xml_node_value(order_temp, prop_value, prop_name="key", value_name="value"):
    node = order_temp.findall(".//*[@{0}='{1}']".format(prop_name, prop_value))
    if node is not None and len(node) > 0:
        return remove_accents(node[0].get(value_name, ""))
    else:
        return ""


def _add_sale_type(report, order_t):
    sale_pod_type = order_t.get("pod_type")

    sale_type = ""
    if sale_pod_type == "DT":
        sale_type = "DRIVE-THRU"
    else:
        if order_t.get("sale_type") != 'EAT_IN':
            sale_type = (order_t.get("sale_type")).replace('_', ' ').upper()
            if sale_type == "APP":
                pickup_type_element = _get_xml_node_value(order_t, "PICKUP_TYPE").upper()

                if pickup_type_element is not None:
                    pickup_type_element = pickup_type_element.get("value", "") \
                        if hasattr(pickup_type_element, 'get') else pickup_type_element
                    if pickup_type_element.upper() == "TAKE_OUT":
                        sale_type = "VIAGEM"

                    if pickup_type_element.upper() == "EAT_IN":
                        sale_type = ""

            if "TAKE" in sale_type:
                sale_type = "VIAGEM"

    if sale_type != "":
        sale_type_text = constants.TXT_4SQUARE + _center_text(sale_type, '*', COLS / 2) + constants.TXT_NORMAL
        report.write("\n%s" % sale_type_text)

    return report


def _create_qrcode(qr_data):
    qr = qrcode.QRCode(
        version=2,
        error_correction=qrcode.constants.ERROR_CORRECT_H,
        box_size=5,
        border=4,
    )

    qr.add_data(qr_data)
    qr.make(fit=True)

    img = qr.make_image()
    img = img.convert("RGB")
    img = printer_wrapper.image(img)

    return img.strip()


def _add_additional_comments(report, production_order):
    tab_add_info = production_order.findall(".//Property[@key='TAB_ADD_INFO']")
    if tab_add_info and len(tab_add_info) > 0:
        tab_add_info_text = tab_add_info[0].attrib["value"]
        report.write("\n{}".format(SMALL_SEPARATOR))

        if len(tab_add_info_text) > COLS:
            report.write("\n\n%s" % remove_accents(tab_add_info_text[:COLS]))
            report.write("\n%s" % remove_accents(tab_add_info_text[COLS:2*COLS]))
        else:
            report.write("\n\n%s" % remove_accents(tab_add_info_text))

        report.write("\n\n{}".format(SMALL_SEPARATOR))

    return report


def _join(dic1, dic2):
    d = dict(dic1)
    d.update(dic2)
    return d


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


def _center(s, cols=COLS):
    if not s:
        return s
    s = _cut(s)
    miss = cols - len(s)
    if miss == 0:
        return s
    left = miss / 2
    return (" " * left) + s


def _center_text(text, sep=" ", line_size=COLS):
    text = " %s " % text
    inicio = sep * int((line_size - len(text)) / 2)
    meio = inicio + text
    fim = sep * (line_size - len(meio))

    return meio + fim


def _get_first_printed_time(production_order):
    production_items = production_order.findall(".//Items") or []
    for item in production_items:
        serverd_tags = item.findall(".//TagHistory/TagEvent[@tag='served']") or []
        if len(serverd_tags) > 0:
            return serverd_tags[0].attrib["timestamp"]

    return None


def _get_picklist_font_size_items():
    # type: () -> str

    global picklist_font_size_items

    if picklist_font_size_items is None:
        small = constants.TXT_FONT_A
        large = constants.TXT_SIZE + chr(constants.TXT_WIDTH[2] + constants.TXT_HEIGHT[2])
        medium = constants.TXT_FONT_SELECT_C + constants.TXT_FONT_C
        try:
            sizes = {"small": small, "medium": medium, "large": large}
            font_size_items = str((config.find_value("Picklist.FontSize") or "small")).strip()
            picklist_font_size_items = sizes[font_size_items]
        except Exception as _:
            picklist_font_size_items = small

    return picklist_font_size_items


def _rollback_printer_default_font():
    # type: () -> str

    return "{}{}{}{}".format(constants.TXT_NORMAL, constants.TXT_BOLD_OFF, constants.TXT_FONT_A, constants.TXT_ALIGN_LT)
