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

import base64
import simplejson as json

from datetime import datetime
from decimal import Decimal as D

from typing import List, Dict, Optional
from xml.etree import cElementTree as eTree

from application.configurator import Configurator
from application.model import EventData
from application.repository import MessageBusRepository


class OrderPictureInteractor(object):

    def __init__(self, configurations, repository):
        # type: (Configurator, MessageBusRepository) -> None

        self.configs = configurations
        self.repository = repository

    def build(self, event_data):
        # type: (EventData) -> str

        order_id = str(event_data.data.get("orderId"))
        session_id = event_data.data.get("sessionId")
        if session_id in [None, "None", ""]:
            order_picture = self.repository.get_order_picture(order_id)
            xml_picture = eTree.XML(order_picture)
            session_id = ""
            if xml_picture.get("sessionId") not in [None, "None", ""]:
                event_data.data = xml_picture
                session_id = xml_picture.get("sessionId")

        sale_type = int(event_data.data.get("saleType") or 0)
        payload = {
            "business-dt": self._get_business_period(event_data),
            "change": float(event_data.data.get("change") or 0),
            "creation-dttm": event_data.data.get("createdAtGMT") + "Z",
            "discount-amount": float(event_data.data.get("discountAmount") or 0),
            "discount-applied": event_data.data.get("discountsApplied"),
            "due-amount": float(event_data.data.get("dueAmount") or 0),
            "exemption": int(event_data.data.get("exemptionCode")) if event_data.data.get("exemptionCode") else None,
            "order-code": order_id,
            "order-discount-amount": float(event_data.data.get("orderDiscountAmount") or 0),
            "order-picture-custom-order-properties": self._get_custom_properties(event_data),
            "order-picture-sale-lines": self._get_sale_lines(event_data),
            "order-picture-tenders": self._get_tenders(event_data),
            "originator-code": str(int(event_data.data.get("originatorId")[-2:])),
            "pod-type": event_data.data.get("podType"),
            "pos-code": self._get_pos_code(event_data, session_id),
            "pos-user-id": self._get_pos_user_id(session_id),
            "price-basis": event_data.data.get("priceBasis"),
            "price-list": event_data.data.get("priceList"),
            "sale-type-id": sale_type if sale_type >= 0 else 0,
            "session": session_id,
            "state-id": int(event_data.data.get("stateId")),
            "store-code": str(self.configs.store_id),
            "tax-applied": float(event_data.data.get("taxTotal") or 0),
            "tax-total": float(event_data.data.get("taxTotal") or 0),
            "tip": float(event_data.data.get("tip") or 0),
            "total-after-discount": float(event_data.data.get("totalAfterDiscount") or 0),
            "total-amount": float(event_data.data.get("totalAmount") or 0),
            "total-gift": 0,
            "total-gross": float(event_data.data.get("totalGross") or 0),
            "total-tender": float(event_data.data.get("totalTender") or 0),
            "type-id": int(event_data.data.get("typeId")),
            "xml-data": self._get_fiscal_xml(event_data),
            "state-history": self._get_state_history(order_picture=event_data.data),
            "xml-data-cancel": self._get_canceled_fiscal_xml(order_picture=event_data.data),
        }

        return json.dumps(payload, use_decimal=True)

    @staticmethod
    def _get_canceled_fiscal_xml(
        order_picture,  # type: eTree.XML
    ):
        # type: (...) -> Optional[str]

        xml_canceled = order_picture.find(".//OrderProperty[@key='CANCELED_FISCAL_XML']")
        if xml_canceled is not None:
            return xml_canceled.get('value')

        return None

    def _get_fiscal_xml(self, event_data):
        # type: (EventData) -> Optional[str]

        fiscal_xml = None
        prop = event_data.data.find("CustomOrderProperties/OrderProperty[@key='FISCAL_XML']")
        if prop is not None:
            fiscal_xml = prop.get("value")
        elif self.configs.fiscal_xml:
            order_id = event_data.data.get("orderId")
            fiscal_xml = self.repository.get_fiscal_xml_from_fiscal_wrapper(order_id)

        if fiscal_xml is None:
            return None

        fiscal_xml = fiscal_xml.replace(" ", "")
        decoded_fiscal_xml = base64.b64decode(fiscal_xml)
        return base64.b64encode(decoded_fiscal_xml)

    @staticmethod
    def _get_business_period(event_data):
        # type: (EventData) -> str

        order_business_period = event_data.data.get("businessPeriod")
        return datetime.strptime(order_business_period, "%Y%m%d").strftime("%Y-%m-%d")

    @staticmethod
    def _get_pos_user_id(session_id):
        # type: (str) -> str

        if session_id == "":
            return '-1'

        for session_data in session_id.split(","):
            key, value = session_data.split("=")
            if key == "user":
                return value

    @staticmethod
    def _get_pos_code(event_data, session_id):
        # type: (EventData, str) -> str

        pos_id = event_data.data.get("posId")
        if pos_id is None and session_id != "":
            for session_data in session_id.split(","):
                key, value = session_data.split("=")
                if key == "pos":
                    pos_id = value
                    break

        if pos_id is None:
            pos_id = event_data.data.get("instance")

        return pos_id

    def _get_custom_properties(self, event_data):
        # type: (EventData) -> Dict

        payload_custom_order_properties = {}
        store_division = str(self.configs.store_division) if self.configs.store_division else None
        if store_division:
            payload_custom_order_properties["store-division"] = store_division

        custom_properties = event_data.data.find("CustomOrderProperties") or []
        for custom_property in custom_properties:
            if custom_property.get("key").upper() != "FISCAL_XML":
                custom_property_key = custom_property.get("value").encode("utf-8")
                payload_custom_order_properties[custom_property.get("key")] = custom_property_key

        return payload_custom_order_properties

    def _get_tenders(self, event_data):
        # type: (EventData) -> List

        payload_order_tenders = []
        for tender_line in event_data.data.findall("TenderHistory/Tender"):
            tender_payload = {
                "change-amount": float(tender_line.get("change") or 0),
                "order-picture-id": int(event_data.data.get("orderId")),
                "reference-amount": float(tender_line.get("tenderAmount") or 0),
                "tender-amount": float(tender_line.get("tenderAmount") or 0),
                "tender-detail": self._get_tender_detail(tender_line),
                "tender-type": tender_line.get("tenderType"),
                "tip": float(tender_line.get("tip")) if tender_line.get("tip") else None
            }
            payload_order_tenders.append(tender_payload)

        return payload_order_tenders

    def _get_tender_detail(self, tender_line):
        tender_detail_json = tender_line.get("tenderDetail")
        if not tender_detail_json:
            return None

        try:
            tender_detail = json.loads(tender_detail_json)
            payment_brand = tender_detail.get("Bandeira")
        except (json.JSONDecodeError, TypeError):
            return None

        if not payment_brand:
            return None

        self._fix_missing_tender_details(tender_detail)
        return json.dumps(tender_detail)

    @staticmethod
    def _fix_missing_tender_details(tender_detail):
        for value in tender_detail.keys():
            if tender_detail[value] is None:
                tender_detail[value] = ""

    def _get_sale_lines(self, event_data):
        # type: (EventData) -> List

        payload_order_sale_lines = []
        for sale_line in event_data.data.findall("SaleLine"):
            sale_line_payload = {
                "added-qty": D(sale_line.get("addedQty")) if sale_line.get("addedQty") else None,
                "added-unit-price": float(sale_line.get("addedUnitPrice") or 0),
                "chosen-qty": D(sale_line.get("chosenQty")) if sale_line.get("chosenQty") else None,
                "comment": self._get_sale_line_comments(sale_line),
                "custom-properties": self._get_sale_line_custom_properties(sale_line),
                "default-qty": D(sale_line.get("defaultQty")) if sale_line.get("defaultQty") else None,
                "inc-qty": D(sale_line.get("incQty") or 0),
                "dec-qty": D(sale_line.get("decQty") or 0),
                "item-discount": float(sale_line.get("itemDiscount") or 0),
                "discount-applied": sale_line.get("discountsApplied"),
                "item-price": float(sale_line.get("itemPrice") or 0),
                "item-type": sale_line.get("itemType"),
                "line-number": int(sale_line.get("lineNumber")),
                "level": int(sale_line.get("level")),
                "multiplied-qty": D(sale_line.get("multipliedQty") or sale_line.get("qty") or 0),
                "qty": D(sale_line.get("qty") or 0),
                "menu-item-code": sale_line.get("itemId"),
                "price-key": sale_line.get("priceKey"),
                "product": sale_line.get("productName").encode("utf-8"),
                "part-code": sale_line.get("partCode"),
                "plu": sale_line.get("partCode"),
                "order-picture-id": int(event_data.data.get("orderId")),
                "sub-qty": D(sale_line.get("subQty")) if sale_line.get("subQty") else None,
                "sub-unit-price": float(sale_line.get("subUnitPrice") or 0),
                "tax-items": self._get_sale_line_tax_items(sale_line),
                "tags": self._get_sale_line_tags(sale_line),
                "unit-price": float(sale_line.get("unitPrice") or 0)
            }
            payload_order_sale_lines.append(sale_line_payload)

        return payload_order_sale_lines

    @staticmethod
    def _get_state_history(
        order_picture,  # type: eTree
    ):
        # type: (...) -> List[dict]

        list_state_history = []
        for state in order_picture.find('StateHistory').findall('State'):
            list_state_history.append({
                'state': state.get('state'),
                'state-id': state.get('stateId'),
                'timestamp': state.get('timestamp'),
                'timestamp-gmt': state.get('timestampGMT'),
            })
        return list_state_history

    @staticmethod
    def _get_sale_line_comments(sale_line):
        # type: (eTree) -> List

        comments = []
        for comment in sale_line.findall("Comment"):
            comments.append(comment.get("comment").encode("utf-8"))

        return comments

    @staticmethod
    def _get_sale_line_custom_properties(sale_line):
        # type: (eTree) -> Dict

        custom_properties = {}
        try:
            custom_properties = json.loads(sale_line.get("customProperties") or {})
        except (json.JSONDecodeError, TypeError):
            pass
        return custom_properties

    @staticmethod
    def _get_sale_line_tax_items(sale_line):
        # type: (eTree) -> List

        tax_items = []
        for order_tax_item in sale_line.findall("TaxItem"):
            tax_item = {
                "base-amount-ad": float(order_tax_item.get("baseAmountAD", 0)),
                "base-amount-bd": float(order_tax_item.get("baseAmountBD", 0)),
                "tax-amount-ad": float(order_tax_item.get("taxAmountAD", 0)),
                "tax-amount-bd": float(order_tax_item.get("taxAmountBD", 0)),
                "tax-index": order_tax_item.get("taxIndex"),
                "tax-rate": float(order_tax_item.get("taxRate", 0)),
                "tax-rule-id": int(order_tax_item.get("taxRuleId"))
            }
            tax_items.append(tax_item)

        return tax_items

    @staticmethod
    def _get_sale_line_tags(sale_line):
        # type: (eTree) -> List

        item_tags = []
        try:
            json_array_tags = json.loads(sale_line.get("jsonArrayTags") or "[]")
            for temp_tag in json_array_tags:
                item_tags.append(temp_tag)
        except (json.JSONDecodeError, TypeError):
            pass

        return item_tags
