# coding=utf-8
import datetime
import json
import os
import re
import zlib

from logging import Logger
from string import upper
from xml.etree import cElementTree as eTree

import msgbus
import sysactions

from interactor.route.order.add_items.dto import ToAddItem
from interactor.route.order.change_items_quantity.dto import ToChangeItems
from model.error_type import ExternalServicesErrorsType, PosValidationErrorsType
from model.exception import ExternalServiceErrors, ValidationError
from model.config import Config
from model.repository import PosRepositoryModel
from model.states import OperatorStates, PosStates, OrderStates
from model.tokens import BusTokens
from mbcontextmessagehandler import MbContextMessageBus
from messagebus import Message
from msgbus import TK_SYS_ACK
from posot import OrderTaker
from sysactions import read_scanner, get_used_service, show_messagebox, get_current_order, print_text
from typing import Optional, Any, Dict, List
from mwhelper import ModelService
from services import NavigationService

currentOrder = "CurrentOrder/Order"


class PosRepository(PosRepositoryModel):

    def __init__(
        self,
        message_bus,         # type: MbContextMessageBus
        logger,              # type: Logger
        config,              # type: Config
        navigation_service,  # type: NavigationService
    ):
        # type: (...) -> None

        self.message_bus = message_bus
        self.logger = logger
        self.config = config
        self.navigation_service = navigation_service
        self.get_nf_type()

        self.languages_translations = {
            "EN": "en-us",
            "ES": "es-es",
            "PT": "pt-br",
        }

        self.descriptions = None

    def get_model(self, pos_id):
        # type: (int) -> eTree

        return sysactions.get_model(pos_id)

    def get_pos_ot(self, model):
        # type: (eTree) -> OrderTaker

        return sysactions.get_posot(model)

    def get_price_list(self, model):
        # type: (eTree) -> str

        pos_id = model.get("posId")
        model_service = ModelService(pos_id=pos_id)
        return model_service.get_price_list_from_model()

    def is_day_opened(self, model):
        # type: (eTree) -> bool

        return model.find("PosState").get("state") == PosStates.OPENED

    def is_day_blocked(self, model):
        # type: (eTree) -> bool

        return model.find("PosState").get("state") == PosStates.BLOCKED

    def is_operator_logged(self, model):
        # type: (eTree) -> bool

        for op in model.findall("Operator"):
            if op.get("state") == OperatorStates.LOGGEDIN:
                return True
        return False

    def translate_message(
        self,
        model,      # type: eTree
        message,    # type: str
    ):
        # type: (...) -> str

        return sysactions.translate_message(
            model=model,
            message_key=message,
        )

    def has_operator_opened(self, model):
        # type: (eTree) -> bool

        for op in model.findall("Operators/Operator"):
            if op.get("state") in [OperatorStates.LOGGEDIN, OperatorStates.OPENED]:
                return True
        return False

    def get_current_operator(self, model):
        # type: (eTree) -> Optional[eTree]

        for op in model.findall("Operator"):
            if op.get("state") == OperatorStates.LOGGEDIN:
                return op
        return None

    def has_current_order(self, model):
        # type: (eTree) -> bool

        order = model.find(currentOrder)
        if order is not None:
            state = order.get("state")
            if state in [OrderStates.IN_PROGRESS, OrderStates.TOTALED]:
                return True
        return False

    def is_order_in_progress(self, model):
        # type: (eTree) -> bool

        order = model.find(currentOrder)
        if order is not None and order.get("state") == OrderStates.IN_PROGRESS:
            return True
        return False

    def is_order_totaled(
        self,
        model,                  # type: eTree
        order_picture=None,     # type: Optional[eTree]
    ):
        # type: (...) -> bool

        pos_id = model.get("posId")
        order = model.find("CurrentOrder/Order")
        order_id = order.get("orderId")
        pos_ot = self.get_pos_ot(model)

        if order_picture is None:
            order_picture = self.get_order_details(pos_id, order_id, pos_ot)

        order_state = order_picture.find(".//Order").get("state")

        return order is not None and order_state == OrderStates.TOTALED

    def get_current_order(self, model):
        # type: (eTree) -> Optional[eTree]

        return model.find(currentOrder)

    def create_order(self, pos_id, pos_ot, price_list="EI", sale_type="EAT_IN"):
        # type: (int, OrderTaker, Optional[str], Optional[str]) -> int

        order = pos_ot.createOrder(pos_id, price_list, saletype=sale_type)
        return int(order.get("orderId"))

    def get_order_details(self, pos_id, order_id, pos_ot):
        # type: (int, int, OrderTaker) -> eTree

        order_str = pos_ot.orderPicture(pos_id, order_id)
        order = eTree.fromstring(order_str) if order_str != "" else None
        return order

    def add_item_to_order(
        self,
        pos_id,                 # type: int
        pos_ot,                 # type: OrderTaker
        price_list,             # type: str
        item,                   # type: ToAddItem
        model,                  # type: eTree
        sale_type="EAT_IN"      # type: Optional[str]
    ):
        # type: (...) -> None

        context = item.context
        line_number = item.line_number
        product_code = item.product_code
        quantity = item.quantity

        if context and line_number:
            item_id = "{}.{}".format(context, product_code)
            sale_line = self._get_sale_line(
                model=model,
                context=context,
                product_code=product_code,
                line_number=line_number,
            )

            if sale_line is None or str(sale_line.get("itemType")) != "INGREDIENT":
                pos_ot.clearOption(pos_id, line_number, "", item_id)
                pos_ot.doOption(pos_id, item_id, quantity, line_number, '@')
            else:
                pos_ot.changeLineItemQty(pos_id, line_number, quantity, itemid=item_id)
        if context is None and line_number is None:
            item_id = "1.{}".format(product_code)
            pos_ot.doSale(pos_id, item_id, price_list, quantity, verifyOption=False, saletype=sale_type)

    def reopen_shopping_cart(
            self,
            pos_id,     # type: int
            pos_ot      # type: OrderTaker
    ):
        # type: (...) -> eTree

        pos_ot.clearTenders(pos_id)
        pos_ot.reopenOrder(pos_id)
        order_str = pos_ot.orderPicture(int(pos_id))
        order = eTree.fromstring(order_str)
        return order

    def order_has_tender(self, model):
        # type: (eTree) -> Optional[eTree]

        for tender in model.findall("CurrentOrder/Order/TenderHistory/Tender") or []:
            if tender.get('tenderType') != '60':
                return True
        return False

    def void_order(
        self,
        pos_id,         # type: int
        pos_ot,         # type: OrderTaker
        order_id="",    # type: Optional[str]
    ):
        # type: (...) -> None

        pos_ot.voidOrder(
            posid=pos_id,
            orderid=order_id,
        )

    def delete_line(self, pos_id, pos_ot, line_numbers):
        # type: (str, OrderTaker, List[int]) -> None

        line_to_delete = "|".join(map(str, line_numbers))
        pos_ot.voidLine(pos_id, line_to_delete)

    def get_pos_list(self):
        # type: () -> tuple

        return sysactions.get_poslist()

    @staticmethod
    def do_tender(
        pos_id,             # type: int
        tender_id,          # type: int
        pos_ot,             # type: OrderTaker
        amount,             # type: float
        tender_details=""   # type: Optional[str]
    ):
        # type: (...) -> Dict

        return pos_ot.doTender(
            posid=pos_id,
            tenderid=tender_id,
            amount=amount,
            donotclose=True,
            tenderdetail=tender_details
        )

    def set_order_to_paid(self, model, pos_ot):
        # type: (eTree, OrderTaker) -> Any

        order = self.get_current_order(model)
        pos_id = order.get("posId")

        xml_order = get_current_order(model)
        all_tenders = xml_order.findall("TenderHistory/Tender")
        if len(all_tenders) == 0:
            raise Exception("Invalid tender amount")

        last_tender = all_tenders[-1]

        last_amount = last_tender.get("tenderAmount", 0)
        if float(last_amount) == 0:
            raise Exception("Invalid tender amount")

        last_tender_type_id = last_tender.get("tenderType")
        last_tender_id = last_tender.get("tenderId")
        pos_ot.doTender(
            posid=int(pos_id),
            tenderid=last_tender_type_id,
            amount=last_amount,
            donotclose=False,
            ordertenderid=last_tender_id
        )

    def add_custom_property(self, key, value, pos_ot):
        # type: (str, str, OrderTaker) -> None

        pos_ot.setOrderCustomProperty(key, value)

    def add_custom_properties(self,  pos_ot, properties, orderid=''):
        # type: (OrderTaker, dict, str) -> None

        pos_ot.setOrderCustomProperties(properties, orderid)

    def update_order_properties(self, pos_ot, posid, saletype):
        # type: (OrderTaker, str, str) -> eTree

        order = pos_ot.updateOrderProperties(posid, saletype=saletype)
        return order

    def do_total(self, pos_id, pos_ot):
        # type: (int, OrderTaker) -> eTree

        pos_ot.doTotal(pos_id)
        order_str = pos_ot.orderPicture(int(pos_id))
        order = eTree.fromstring(order_str)
        return order

    def get_rupture_products(self):
        # type: () -> list

        message = Message(BusTokens.TK_REMOTE_ORDER_GET_RUPTURED_ITEMS)
        response_message = self.message_bus.send_message("RemoteOrder", message)
        if response_message.token == TK_SYS_ACK:
            return response_message.data

    def get_menu_data(self):
        # type: () -> bytearray

        try:
            with open("menu.json") as menu_file:
                data = self._filter_products(menu_file=menu_file)
                json_data = json.loads(data)
                menu_id = self.get_catalog_version()
                new_json_data = {
                    "products": json_data,
                    "menuId": menu_id if menu_id else ''
                }
                data = json.dumps(new_json_data)

                compress_obj = zlib.compressobj(zlib.Z_BEST_COMPRESSION, zlib.DEFLATED, 31)
                zipped_menu = compress_obj.compress(data)
                zipped_menu += compress_obj.flush()
                return zipped_menu
        except Exception as _:
            self.logger.exception("Error getting menu.json products")

        return ""

    def get_nf_type(self):
        # type: () -> None

        if self.config.nf_type:
            return

        message = Message(BusTokens.TK_FISCALWRAPPER_GET_NF_TYPE)
        response_message = self.message_bus.send_message("FiscalWrapper", message)
        if response_message.token == TK_SYS_ACK:
            self.config.nf_type = response_message.data.upper()
        else:
            self.logger.error(response_message.data)
            raise ExternalServiceErrors(ExternalServicesErrorsType.ServiceUnavailable)

    def get_sefaz_status(self):
        # type: () -> bool

        message = Message(BusTokens.TK_FISCALWRAPPER_SEFAZ_CONNECTIVITY)
        response_message = self.message_bus.send_message("FiscalWrapper", message)
        if response_message.token == TK_SYS_ACK:
            return True if response_message.data else False
        else:
            self.logger.error(response_message.data)

    def get_store_sat_status(
        self,
    ):
        # type: (...) -> bool

        message = Message(token=BusTokens.TK_FISCALWRAPPER_SAT_STORE_STATUS)
        response_message = self.message_bus.send_message(
            component="FiscalWrapper",
            message=message,
        )
        if response_message.token == TK_SYS_ACK:
            result = response_message.data.lower() == "true"
            return result
        else:
            self.logger.error(response_message.data)
            return False

    def get_catalog_version(
        self,
    ):
        # type: (...) -> Optional[str]

        message = Message(BusTokens.TK_POS_UPDATER_GET_VERSIONS, timeout=5000000)
        response_message = self.message_bus.send_message("PosUpdater", message)
        if response_message.token == TK_SYS_ACK:
            data = json.loads(response_message.data or '{}')
            data = data["versions"] if "versions" in data else {}
            return data["catalog"] if "catalog" in data else None
        else:
            self.logger.error(response_message.data)

        return None

    def void_tenders_and_order(self, pos_id, pos_ot):
        # type: (int, OrderTaker) -> Any

        pos_ot.clearTenders(str(pos_id))
        pos_ot.voidOrder(str(pos_id))

    def logout_delivery_user(self, pos_id, operator_id):
        # type: (int, int) -> bool

        message = Message(msgbus.TK_POS_USERLOGOUT,
                          msgbus.FM_PARAM, "%s\0%s" % (pos_id, operator_id),
                          timeout=10000000)
        logout_msg = self.message_bus.send_message("POS%d" % int(pos_id), message)
        if logout_msg.token == msgbus.TK_SYS_ACK:
            return True
        else:
            self.logger.error(logout_msg.data)
            return False

    def close_business_period(self, pos_id):
        # type: (int) -> bool
        self.logger.info("Closing business current period")

        message = Message(msgbus.TK_POS_BUSINESSEND, msgbus.FM_PARAM, "%s" % pos_id, timeout=10000000)
        business_end_msg = self.message_bus.send_message("POS%d" % int(pos_id), message)
        if business_end_msg.token == msgbus.TK_SYS_ACK:
            return True
        else:
            self.logger.error(business_end_msg.data)
            return False

    def open_business_period(self, pos_id):
        # type: (int) -> bool
        self.logger.info("Opening business period")

        current_date = datetime.datetime.now().date().strftime("%Y%m%d")
        message = Message(msgbus.TK_POS_BUSINESSBEGIN,
                          msgbus.FM_PARAM, "%s\0%s" % (int(pos_id), current_date),
                          timeout=10000000)
        business_begin_msg = self.message_bus.send_message("POS%d" % int(pos_id), message)
        if business_begin_msg.token == msgbus.TK_SYS_ACK:
            return True
        else:
            self.logger.error(business_begin_msg.data)
            return False

    def authenticate_user(self, user_id, user_password):
        # type: (str, str) -> bool

        self.logger.info("Authenticate delivery user")
        message = Message(msgbus.TK_USERCTRL_AUTHENTICATE,
                          msgbus.FM_PARAM,
                          '%s\x00%s\x00' % (user_id.encode("utf-8"), user_password.encode("utf-8")),
                          timeout=10000000)
        authenticate_msg = self.message_bus.send_message('UserControl', message)
        if authenticate_msg.token == msgbus.TK_SYS_ACK:
            return authenticate_msg.data
        else:
            self.logger.error(authenticate_msg.data)
            return False

    def open_user(self, pos_id, user_id, long_user_name):
        # type: (int, str, str) -> bool

        self.logger.info("Opening delivery user")
        message = Message(msgbus.TK_POS_USEROPEN,
                          msgbus.FM_PARAM,
                          "%s\0%s\0%s\0%s" % (pos_id, user_id.encode("utf-8"), 0.0, long_user_name),
                          timeout=10000000)
        open_user_message = self.message_bus.send_message("POS%d" % int(pos_id), message)
        if open_user_message.token == msgbus.TK_SYS_ACK:
            return True
        else:
            self.logger.error(open_user_message.data)
            return False

    def login_user(self, pos_id, user_id, long_user_name):

        self.logger.info("Logging delivery user")
        message = Message(msgbus.TK_POS_USERLOGIN,
                          msgbus.FM_PARAM,
                          "%s\0%s\0%s" % (pos_id, user_id.encode("utf-8"), long_user_name),
                          timeout=10000000)
        login_msg = self.message_bus.send_message("POS%d" % int(pos_id), message)
        if login_msg.token == msgbus.TK_SYS_ACK:
            return True
        else:
            self.logger.error(login_msg.data)
            return False

    def get_read_scanner(self, pos_id):
        # type: (int) -> str

        message = "$PUT_CODE_IN_SCANNER_BELOW"
        title = "Scanner"
        timeout = 60000

        model = self.get_model(pos_id)

        current_scanner = get_used_service(model, "scanner")
        if not current_scanner:
            raise ValidationError(PosValidationErrorsType.ScannerNotFound)

        dlg_id = show_messagebox(pos_id, message=message, title=title, buttons="$CANCEL", asynch=True, timeout=timeout,
                                 focus=False)

        try:
            scanner_result = read_scanner(current_scanner, 60000, dlg_id, pos_id)
            if "," in scanner_result:
                response_code, read = scanner_result.split(",")
                if response_code != "0":
                    return
            else:
                return scanner_result
        except Exception as _:
            show_messagebox(pos_id, message="$READING_MISTAKE", icon="error", timeout=180000)

    def print_text(self, pos_id, content):
        # type: (str, str) -> None

        if not content:
            raise ValidationError(PosValidationErrorsType.EmptyPrinterContent)

        model = self.get_model(pos_id)

        current_printer = get_used_service(model, "printer")
        if not current_printer:
            raise ValidationError(PosValidationErrorsType.PrinterNotFound)

        result = print_text(pos_id, model, content, force_printer=current_printer)
        if not result:
            raise ExternalServiceErrors(ExternalServicesErrorsType.ErrorPrinting, current_printer)

    def get_storewide_config(self, parameter, defval=None, array=False):
        # type: (str, Optional[bool], Optional[bool]) -> list

        configs = sysactions.get_storewide_config(parameter, defval, array)

        return configs

    def apply_coupon(
            self,
            pos_id,         # type: int
            coupon_code     # type: str
    ):
        # type: (...) -> (str, bool)

        payload = json.dumps({
            'posId': pos_id,
            'voucherId': coupon_code.upper(),
            'originatorPosId': pos_id
        })

        message = Message(BusTokens.TK_COUPON_APPLY,
                          msgbus.FM_PARAM, payload)
        coupon_msg = self.message_bus.send_message('nCoupon', message)
        if coupon_msg.token == msgbus.TK_SYS_ACK:
            coupon_applied = True
        else:
            self.logger.error(coupon_msg.data)
            coupon_applied = False

        return coupon_msg.data, coupon_applied

    def get_order_custom_properties(self, pos_ot, key, order_id=""):
        # type: (OrderTaker, str, int) -> eTree

        properties = pos_ot.getOrderCustomProperties(key, order_id)
        return properties

    def get_cfg(self, pos_id, key, default):
        # type: (int, str, str) -> Any

        configs = sysactions.get_cfg(pos_id).key_value(key, default)
        return configs

    def change_line_quantity(self, model, pos_ot, request):
        # type: (eTree.Element, OrderTaker, List[ToChangeItems]) -> Any

        pos_id = model.get("posId")
        to_change_items = []

        for item in request:
            if item.new_quantity == 0:
                raise ValidationError(PosValidationErrorsType.InvalidQuantity)

            order_sale_lines = get_current_order(model).findall("SaleLine")
            selected_sale_line = None
            for sale_line in order_sale_lines:
                current_line_number = sale_line.get("lineNumber")
                current_level = sale_line.get("level")
                current_code = int(sale_line.get("partCode") or 0)
                current_context = sale_line.get("itemId")
                if current_context == item.context and current_code == item.product_code and \
                        current_line_number == str(item.line_number) and current_level == "0":
                    selected_sale_line = sale_line
                    break

            if selected_sale_line is None:
                raise ValidationError(PosValidationErrorsType.SaleItemsInvalid)

            item_type = selected_sale_line.get("itemType")

            if item_type == "COMBO":
                raise ValidationError(PosValidationErrorsType.ItemTypeComboInvalid)

            to_change_items.append(item)

        for item in to_change_items:
            item_id = "{}.{}".format(item.context, item.product_code)
            pos_ot.changeLineItemQty(pos_id, str(item.line_number), item.new_quantity, item_id)

    def remove_coupon(
            self,
            pos_id,             # type: int
            coupon_applied_id   # type: str
    ):
        # type: (...) -> (str, bool)

        data = json.dumps({
            'pos_id': pos_id,
            'benefit': coupon_applied_id
        })

        message = Message(
            token=BusTokens.TK_COUPON_REMOVE,
            data_type=msgbus.FM_PARAM,
            data=data
        )
        response = self.message_bus.send_message('nDiscount', message)

        if response.token == msgbus.TK_SYS_ACK:
            coupon_removed = True
        else:
            self.logger.error(response.data)
            coupon_removed = False

        return response.data, coupon_removed

    def _get_languages(
        self,
        uppercase=False     # type: Optional[bool]
    ):
        # type: (...) -> list

        message = Message(
            token=BusTokens.TK_I18N_GETLANGS,
            data_type=msgbus.FM_PARAM
        )
        response = self.message_bus.send_message(
            component="I18N",
            message=message
        )
        languages = []
        if response.token == TK_SYS_ACK:
            languages = sorted(set(re.findall(r'[a-z]{2}', response.data)))
        if uppercase:
            languages = [l.upper() for l in languages]

        return languages

    def get_text_from_i18n(
            self
    ):
        # type: () -> Dict

        l10n_params = [
            "L10_PYTHON_DATE_FORMAT",
            "L10_PYTHON_DATETIME_FORMAT",
            "L10N_DECIMAL_PLACES",
            "L10N_CURRENCY_DECIMALS",
            "L10N_CURRENCY_SYMBOL",
            "L10N_DECIMALS_SEPARATOR",
            "L10N_THOUSANDS_SEPARATOR",
            "L10N_CURRENCY_SYMBOL_POSITION"
        ]
        languages = self._get_languages()

        text = {}
        for language in languages:
            l10n_param = language + "\0"
            l10n_param += "\0".join(l10n_params)

            message = Message(
                token=BusTokens.TK_I18N_GETTABLEJSON,
                data_type=msgbus.FM_PARAM,
                data=l10n_param
            )
            response_message = self.message_bus.send_message(
                component="I18N",
                message=message
            )
            if response_message.token == TK_SYS_ACK:
                text[self.languages_translations[language.upper()]] = json.loads(response_message.data)
            else:
                raise ExternalServiceErrors(ExternalServicesErrorsType.LanguageValuesNotFound, language.upper())

        return text

    def get_tender_types(
            self
    ):
        # type: () -> Any

        return sysactions.get_tender_types()

    @staticmethod
    def _get_customer_info(
            data    # type: Dict[str, Any]
    ):
        # type: (...) -> Dict[str, str]

        customer_info = data.get("customerInfo", "")
        document_info = customer_info.get("document", None)
        name_info = customer_info.get("name", None)
        valid_states = ["onStart", "onTotal"]

        document = None
        if document_info and any([d in valid_states for d in document_info]):
            document = "INITIAL" if "onStart" in document_info else "FINAL"

        name = None
        if name_info and any([n in valid_states for n in name_info]):
            name = "INITIAL" if "onStart" in name_info else "FINAL"

        return dict(document=document, name=name)

    def _send_message_to_pos(
            self,
            data,   # type: str
            pos_id  # type: int
    ):
        # type: (...) -> Message

        message = Message(
            token=BusTokens.TK_POS_EXECUTEACTION,
            data_type=msgbus.FM_PARAM,
            data=data
        )
        return self.message_bus.send_message(
            component="POS{}".format(pos_id),
            message=message
        )

    def get_totem_languages(
        self
    ):
        # type: (...) -> List[str]

        all_languages = self._get_languages(uppercase=True)
        if not all_languages:
            return []

        if not self.config.totem_config_path or not os.path.exists(self.config.totem_config_path):
            return []

        config_file = open(self.config.totem_config_path, 'rb')
        data = json.load(config_file)
        totem_languages = data.get("languages", [])

        filtered_languages = [self.languages_translations[l] for l in all_languages if l in totem_languages]

        return filtered_languages

    def get_static_config(
        self,
        pos_id  # type: str
    ):
        # type: (...) -> Dict[str, Any]

        data = "\0".join(map(str, ["get_static_config", pos_id]))
        response = self._send_message_to_pos(
            data=data,
            pos_id=pos_id
        )

        if response.token != msgbus.TK_SYS_ACK:
            raise ExternalServiceErrors(ExternalServicesErrorsType.CannotGetStoreConfigForPos, pos_id)

        data = json.loads(response.data or "{}")
        if data:
            customer_info = self._get_customer_info(data)

            available_sale_types = data.get("availableSaleTypes", {})
            sale_type_quick = available_sale_types.get("saleTypeQuick", {})
            show_take_out_screen = any(["TAKE_OUT" in sale_types for sale_types in sale_type_quick])

            totem_configuration = data.get("totemConfigurations", {})
            totem_products = totem_configuration.get("products", {})
            languages = self.get_totem_languages()
            cancel_timeout_window = data.get("cancelTimeoutWindow", -1)
            screen_timeout = data.get("screenTimeout", -1)
            discount_config = totem_configuration.get("discount", {})
            show_discount = discount_config.get("showDiscount", True)

            return dict(
                CUSTOMER_INFO=customer_info,
                SHOW_TAKE_OUT_SCREEN=show_take_out_screen,
                SHOW_SCANNER_SCREEN=data.get("totemQRCodeEnabled", False),
                SHOW_RUPTURE_ITEMS=data.get("showRupturedProducts", False),
                SHOW_POPUP_COMBO=totem_products.get("showPopupCombo", False),
                LANGUAGES=languages,
                CANCEL_TIMEOUT_WINDOW=cancel_timeout_window,
                SCREEN_TIMEOUT=screen_timeout,
                SHOW_DISCOUNTS=show_discount,
            )

        raise ValidationError(PosValidationErrorsType.EmptyStoreConfigurationForPos)

    def do_clear_tender(
            self,
            pos_id,      # type: str
            model,       # type: object
            tender_id    # type: int
    ):
        # type: (...) -> float

        pos_ot = self.get_pos_ot(model)

        current_order = sysactions.get_current_order(model)
        tenders = current_order.findall(".//TenderHistory/Tender")

        tender_ids = [int(t.get("tenderId")) for t in tenders if t.get("tenderId", None) is not None]

        if tender_id not in tender_ids:
            raise ValidationError(PosValidationErrorsType.InvalidTenderId)

        pos_ot.clearTenders(
            pos_id,
            None,
            tender_id
        )

        current_order = pos_ot.orderPicture(pos_id)
        current_order = eTree.XML(current_order)
        return current_order.get("dueAmount")

    def get_navigation(
            self,
            pos_id  # type: int
    ):
        # type: (...) -> Dict

        data = "\0".join(map(str, ["get_navigation_data", pos_id]))
        response = self._send_message_to_pos(
            data=data,
            pos_id=pos_id
        )

        if response.token != msgbus.TK_SYS_ACK:
            raise ExternalServiceErrors(ExternalServicesErrorsType.CannotGetNavigationForPos, pos_id)

        navigation = json.loads(response.data or "{}")

        if not self.descriptions:
            self.descriptions = self.navigation_service.get_navigation_descriptions()

        self._get_navigation_descriptions(
            parent_nav=navigation,
        )

        return navigation

    def _get_navigation_descriptions(
        self,
        parent_nav,    # type: dict
    ):
        # type: (...) -> None

        for nav in parent_nav:
            nav_id = nav.get("nav_id")
            nav["navigation_text"] = {
                "pt-br": "",
                "en-us": "",
                "es-es": "",
            }

            if nav_id in self.descriptions:
                nav["navigation_text"] = self.descriptions.get(nav_id)

            groups = nav.get("groups")
            if groups:
                self._get_navigation_descriptions(
                    parent_nav=groups,
                )

    def do_stored_order(
        self,
        pos_id,     # type: int
        model,      # type: eTree
    ):
        # type: (...) -> None

        pos_ot = self.get_pos_ot(
           model=model,
        )

        pos_ot.storeOrder(
           posid=pos_id,
        )

    def get_manager_status(
        self,
        pos_id,     # type: int
    ):
        # type: (...) -> bool

        model = self.get_model(pos_id=pos_id)
        totem_was_closed = sysactions.get_custom(
            model=model,
            key="TOTEM_WAS_CLOSED",
        )
        if totem_was_closed is None:
            return False
        else:
            if upper(totem_was_closed) == "TRUE":
                return True
            elif upper(totem_was_closed) == "FALSE":
                return False
            else:
                raise Exception("Totem status unavailable")

    def _filter_products(
        self,
        menu_file,  # type: file
    ):
        # type: (...) -> str

        products = json.load(fp=menu_file)
        filtered_products = self._filter_sons(
            products=products,
            pos_type="TT",
        )

        return json.dumps(obj=filtered_products)

    def _filter_sons(
        self,
        products,   # type: List[Dict]
        pos_type,   # type: str
    ):
        # type: (...) -> List[Dict]

        filtered_products = []
        for product in products:
            if pos_type not in product.get("product", {}).get("hideOnPosType", []):
                product["sons"] = self._filter_sons(
                    products=product.get("sons", []),
                    pos_type=pos_type,
                )
                filtered_products.append(product)

        return filtered_products

    def _get_sale_line(
        self,
        model,              # type: eTree
        context,            # type: str
        product_code,       # type: str
        line_number,        # type: str
    ):
        # type: (...) -> Optional[eTree.Element]

        sale_lines = get_current_order(model=model).findall("SaleLine")
        for line in sale_lines:
            exists = self._exists_item(
                line=line,
                context=context,
                product_code=product_code,
                line_number=line_number,
            )

            if exists:
                return line

        return None

    @staticmethod
    def _exists_item(
        line,           # type: eTree.Element
        context,        # type: str
        product_code,   # type: str
        line_number,    # type: str
    ):
        sale_line_line_number = str(line.get("lineNumber"))
        sale_line_code = str(line.get("partCode") or "")
        sale_line_context = str(line.get("itemId"))

        return (
            sale_line_context == str(context) and
            sale_line_code == str(product_code) and
            sale_line_line_number == str(line_number)
        )

