from logging import Logger
from xml.etree import cElementTree as eTree
from xml.etree.ElementTree import Element

from application.model.exceptions import ProductNotFound
from typing import (
    Dict,
    List,
    Optional
)

from sysactions import get_model, get_posot, get_pricelist

from application.model.benefit import Benefit
from application.model.order import Order
from application.model.conditions import Restrictions
from application.repository import NDiscountRepository
from application.service import OrderService


class AddBenefitToApplyUnitOfWork(object):
    def __init__(
            self,
            logger,                     # type: Logger
            pos_id,                     # type: int
            benefit,                    # type: Benefit
            benefit_applier,            # type: str
            tender_id,                  # type: str
            order_picture,              # type: Element
            order_service,              # type: OrderService
            repository,                 # type: NDiscountRepository
            product_groups,             # type: Dict
            table_id=None,              # type: Optional[str]
            user_auth_level=False       # type: Optional[bool]
    ):
        # type: (...) -> None

        self.logger = logger
        self.pos_id = pos_id
        self.benefit = benefit
        self.benefit_key = None
        self.benefit_id = benefit.id
        self.raw_benefit = benefit.raw
        self.benefit_applier = benefit_applier
        self.tender_id = tender_id
        self.order_service = order_service
        self.repository = repository
        self.product_groups = product_groups
        self.added_sale_lines = []
        self.promotion_tenders = []
        self.order_tenders_ids = []
        self.benefit_order_info = None
        self.user_auth_level = user_auth_level

        self.model = get_model(pos_id)
        self.order = self.order_service.get_order(
            self.pos_id,
            create_order=any([a.name == "itemsToAdd" for a in self.benefit.actions]),
            order_picture=order_picture,
            model=self.model,
            table_id=table_id
        )

        self.pos_ot = get_posot(self.model)
        self.price_list = get_pricelist(self.model)

    def update_order(self):
        self.order = self.order_service.get_order(
            self.pos_id,
            create_order=any([a.name == "itemsToAdd" for a in self.benefit.actions])
        )

    def add_sale_line(self, part_code, is_free, price, tender_id, burnt_items, restriction, on_total, action_id):
        # type: (int, bool, float, int, Dict, Restrictions, bool, int) -> Dict

        if int(part_code) not in self.repository.get_all_products():
            raise ProductNotFound()

        try:
            self.pos_ot.blkopnotify = True
            added_line_number = self._sale_item(part_code, 1, on_total)
            self._overwrite_item_price(added_line_number, price, is_free)
            self.update_order()
            items_to_burn = self.order.get_items_to_burn(self.order, self.benefit, burnt_items, self.product_groups)
            required_items = Order.get_required_items(self.order, self.benefit)
        finally:
            self.pos_ot.blkopnotify = False

        if on_total:
            self._set_item_custom_properties(added_line_number, part_code, items_to_burn, required_items, restriction)
        else:
            self._set_item_custom_properties(added_line_number, part_code, items_to_burn, required_items)

        added_sale_lines = [x for x in self.order.sale_lines if int(x.line_number) == int(added_line_number)]
        tender_amount = self._get_tender_amount(added_sale_lines, is_free, price)

        if tender_amount > 0:
            self.promotion_tenders.append({"action_id": action_id, "amount": tender_amount, "tender_id": tender_id})

        self._get_item_to_burn_from_sale_lines(self.benefit, added_sale_lines, items_to_burn, part_code)
        return items_to_burn

    @staticmethod
    def _get_item_to_burn_from_sale_lines(benefit, added_sale_lines, items_to_burn, part_code):
        if not benefit.conditions.burn_items:
            return

        for sale_line in added_sale_lines:
            if sale_line.item_id == str(part_code):
                key = "{}.{}.{}.{}".format(sale_line.line_number, sale_line.level, sale_line.context, sale_line.item_id)
                if key in items_to_burn:
                    items_to_burn[key] += sale_line.qty
                else:
                    items_to_burn[key] = sale_line.qty

    def _set_item_custom_properties(self, added_sale_line_number, part_code, items_to_burn, required_items,
                                    restriction=None):
        # type: (int, int, Dict, List, Restrictions) -> None

        properties = {"benefitKey": self.benefit_key}
        self._set_discount_parent_sale_line(items_to_burn, required_items, properties)
        self._set_item_seat_and_service_period(items_to_burn, required_items, properties)
        self._set_item_sale_type(properties, restriction)
        self.pos_ot.setOrderCustomProperties(properties, "", added_sale_line_number, "1", "0", part_code)

    def _set_discount_parent_sale_line(self, items_to_burn, required_items, properties):
        # type: (Dict, List, Dict) -> None

        items = None
        if items_to_burn:
            items = [x for x in self.order.sale_lines if x.key == next(iter(items_to_burn))]
        elif len(required_items) > 0:
            items = [x for x in self.order.sale_lines if x.key == next(iter(required_items))]

        if items:
            line_number = items[0].line_number
            properties.update({"relationLineNumber": line_number})

    def _set_item_seat_and_service_period(self, items_to_burn, required_items, properties):
        # type: (Dict, List, Dict) -> None

        items = None
        if items_to_burn:
            items = [x for x in self.order.sale_lines if x.key == next(iter(items_to_burn))]
        elif len(required_items) > 0:
            items = [x for x in self.order.sale_lines if x.key == next(iter(required_items))]

        if items:
            seat = items[0].seat
            if seat:
                properties.update({"seat": seat})

            service_period = items[0].service_period.str_json
            if service_period:
                properties.update({"servicePeriod": service_period})

    def _set_item_sale_type(self, properties, restriction=None):
        # type: (Dict, Restrictions) -> None

        if restriction and restriction.valid_sale_types and len(restriction.valid_sale_types) > 0:
            sale_type = restriction.valid_sale_types[0]
        else:
            sale_type = self.order.sale_type if self.order.sale_type != "EAT_IN" else None

        if sale_type and self.order.table_id:
            properties.update({"saleType": sale_type})

    @staticmethod
    def _get_tender_amount(added_sale_lines, is_free, price):
        # type: (List, bool, float) -> float

        tender_amount = 0
        for sale_line in added_sale_lines:
            if price > 0 and is_free:
                tender_amount += sale_line.item_price

        return tender_amount

    def _overwrite_item_price(
            self,
            added_sale_line_number,   # type: int
            price,                    # type: Optional[float]
            is_free                   # type: bool
    ):
        # type (...) -> None

        price = 0 if is_free else price
        if price is not None:
            self.pos_ot.priceOverwrite(self.pos_id, added_sale_line_number, str(price))
            self.logger.info("Price overwritten. New Price: {}".format(str(price)))

    def _sale_item(self, item_id, quantity, on_total):
        # type (int, int, bool) -> int

        after_total = "1" if on_total else "0"
        context_id = "1.%s" % item_id

        params = [self.pos_id, context_id, self.price_list, quantity]
        sale_response = self.pos_ot.doSale(*params, aftertotal=after_total, applydefopts=True)
        added_sale_line = eTree.XML(sale_response)
        added_sale_line_number = int(added_sale_line.get("lineNumber"))

        self.added_sale_lines.append(added_sale_line_number)
        self.logger.info("New sale line added. LineNumber: {}; ItemId: {}".format(added_sale_line_number, context_id))

        return added_sale_line_number

    def set_benefit_key(self, benefit_key):
        self.benefit_key = benefit_key
