# -*- coding: utf-8 -*-

import json

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

import sysactions

from messagebus import (
    DataType,
    DefaultToken,
    Message,
    MessageBus,
)
from msgbus import TK_SLCTRL_OMGR_ORDERPICT
from posot import (
    OrderTaker,
    OrderTakerException,
)

from application.model import (
    Configurations,
    NDiscountModelJsonEncoder,
)
from application.model.benefit import BenefitList
from application.model.exceptions import (
    OrderNotFoundException,
    OrderPictureException,
)
from application.model.order import Order
from application.repository import NDiscountRepository


class OrderService(object):
    def __init__(self, configs, repository, message_bus):
        # type: (Configurations, NDiscountRepository, MessageBus) -> None

        self.configs = configs
        self.logger = configs.logger
        self.repository = repository
        self.message_bus = message_bus

    @staticmethod
    def get_burnt_items(applied_benefits):
        # type: (eTree) -> Dict

        burnt_items = {}
        for benefit in applied_benefits:
            current_burnt_items = benefit["burnt_items"]
            for key in current_burnt_items.keys():
                if key in burnt_items:
                    burnt_items[key] += current_burnt_items[key]
                else:
                    burnt_items[key] = current_burnt_items[key]

        return burnt_items

    def get_applied_benefits_from_order_picture(
        self,
        pos_id,                                     # type: int
        order_picture=None,                         # type: Optional[eTree]
    ):
        # type: (...) -> List

        order_benefits = self.get_benefits_from_order_picture(pos_id, order_picture)
        if not order_benefits:
            return []

        return json.loads(order_benefits.get("benefitList", "[]"))

    def get_benefit_pre_total_sequence_from_order_picture(
        self,                   # type: OrderService
        pos_id,                 # type: int
        order_picture=None,     # type: Optional[eTree]
    ):
        # type: (...) -> List

        order_benefits = self.get_benefits_from_order_picture(
            pos_id=pos_id,
            order_picture=order_picture,
        )
        if not order_benefits:
            return []

        return order_benefits.get("benefitPreTotalSequence", [])

    def get_benefits_from_order_picture(self, pos_id, order_picture=None):
        # type: (int, Optional[eTree]) -> Dict

        try:
            if order_picture is None:
                order_picture = self.get_order_picture(pos_id)

            value = order_picture.find(".//OrderProperty[@key=\"BENEFIT_LIST\"]").get("value")
            return json.loads(value)
        except OrderTakerException:
            pass
        except AttributeError:
            pass
        return {}

    @staticmethod
    def save_benefit_list_custom_property(
        pos_ot,         # type: OrderTaker
        benefit_list,   # type: Union[BenefitList, str]
    ):
        # type: (...) -> Optional[eTree.Element]

        if benefit_list == "clear":
            benefit_list_json = benefit_list
        else:
            benefit_list_json = json.dumps(benefit_list, encoding="utf-8", cls=NDiscountModelJsonEncoder)

        order_xml = pos_ot.setOrderCustomProperty("BENEFIT_LIST", benefit_list_json)

        try:
            return eTree.XML(order_xml)
        except (TypeError, ParseError):
            return None

    def get_order_picture(
        self,
        pos_id,         # type: Any[Union[str, int]]
        order_id=None,  # type: Optional[str]
    ):
        # type: (...) -> Optional[eTree.Element]

        data = "{}\0{}".format(str(pos_id), str(order_id or ''))
        message = Message(TK_SLCTRL_OMGR_ORDERPICT, DataType.param, data, 30000000)
        reply = self.message_bus.send_message("ORDERMGR{}".format(str(pos_id)), message)

        if reply.token == DefaultToken.TK_SYS_ACK:
            try:
                split_reply = reply.data.split("\0")
                if not split_reply[2]:
                    return None
                order_picture = eTree.XML(split_reply[2])
                if order_id:
                    order_picture = order_picture.findall('.//Order')[0]
                return order_picture
            except AttributeError:
                return None

        raise OrderPictureException("Error getting OrderPicture: {} - {}".format(reply.token, reply.data))

    def get_order(
        self,
        pos_id,                 # type: int
        model=None,             # type: Optional[eTree.Element]
        create_order=False,     # type: Optional[bool]
        order_picture=None,     # type: Optional[eTree.Element]
        static_uow=None,        # type: Optional[Any]
    ):
        # type: (...) -> Order

        if not order_picture:
            order_picture = self.get_order_picture(pos_id)

        table_id = static_uow.table_id if static_uow is not None else None
        price_list = static_uow.price_list if static_uow is not None else None
        state = order_picture.get("state") or "" if order_picture else ""
        if state not in ["IN_PROGRESS", "TOTALED"]:
            if not create_order:
                raise OrderNotFoundException()

            order_picture = self._create_new_order(
                pos_id=pos_id,
                model=model,
                price_list=price_list,
                table_id=table_id,
            )

        sale_lines = order_picture.findall(".//SaleLine")

        return Order(
            order_picture=order_picture,
            sale_lines=sale_lines,
            model=model,
            static_uow=static_uow,
        )

    @staticmethod
    def void_order(pos_id, pos_ot):
        # type: (int, OrderTaker) -> None

        pos_ot.voidOrder(pos_id)

    @staticmethod
    def void_line(pos_id, pos_ot, lines_to_void):
        # type: (int, OrderTaker, List) -> None

        if len(lines_to_void) > 0:
            lines_to_void = "|".join([str(x) for x in lines_to_void])
            pos_ot.voidLine(pos_id, lines_to_void)

    @staticmethod
    def clear_discounts(pos_ot, used_discount_ids):
        # type: (OrderTaker, List) -> None

        for discount_id in used_discount_ids:
            pos_ot.clearDiscount(discount_id)

    @staticmethod
    def clear_tenders(pos_id, pos_ot, used_order_tender_ids):
        # type: (int, OrderTaker, List) -> None

        for order_tender_id in used_order_tender_ids:
            pos_ot.clearTenders(pos_id, None, order_tender_id)

    def _create_new_order(
        self,
        pos_id,                 # type: int
        model,                  # type: eTree.Element
        price_list=None,        # type: Optional[str]
        sale_type="EAT_IN",     # type: Optional[str]
        table_id=None,          # type: Optional[str]
    ):
        # type: (...) -> eTree.Element

        # TODO: Review parameters when there is no Order (PriceList and SaleType)
        if price_list is None:
            price_list = "EI"

        pos_ot = sysactions.get_posot(model)
        pos_ot.blkopnotify = True
        order = pos_ot.createOrder(
            posid=pos_id,
            pricelist=price_list,
            saletype=sale_type,
        )

        updated_order = None
        if table_id:
            order_xml = pos_ot.setOrderCustomProperty(
                key="TABLE_ID",
                value=table_id,
                orderid=order.get("orderId"),
            )
            try:
                updated_order = eTree.XML(order_xml)
            except (TypeError, ParseError):
                pass
        pos_ot.blkopnotify = False

        if updated_order is None:
            updated_order = self.get_order_picture(pos_id=pos_id)

        return updated_order

    def get_persisted_benefits_from_order_picture(
        self,
        pos_id,                 # type: int
        order_picture=None,     # type: Optional[eTree]
    ):
        # type: (...) -> Dict

        benefit_list = self.get_benefits_from_order_picture(
            pos_id=pos_id,
            order_picture=order_picture,
        )
        if not benefit_list:
            return {}

        return benefit_list.get("persistedBenefits", {})
