import base64
import json
from datetime import datetime
from typing import (
    Dict,
    List,
    Optional,
    Any,
)

from xml.etree import cElementTree as eTree

from ._Type import JSON
from ._util import get_tender_detail


class OrderFormatter:

    def __init__(self):
        return

    def get_order_picture_dict(
        self,                   # type: OrderFormatter
        order_picture,          # type: eTree.Element
    ):
        # type: (...) -> JSON

        session_id = self._get_order_picture_property(
            order_picture=order_picture,
            order_property="sessionId",
        )
        sale_type = int(order_picture.get("saleType", 0))

        order_picture_dict = {
            "orderId": str(order_picture.get("orderId")),
            "businessPeriod": self._get_business_period(order_picture=order_picture),
            "change": float(order_picture.get("change", 0)),
            "createdAt": order_picture.get("createdAt"),
            "createdAtGMT": order_picture.get("createdAtGMT") + "Z",
            "discountAmount": float(order_picture.get("discountAmount", 0)),
            "discountsApplied": order_picture.get("discountsApplied"),
            "dueAmount": float(order_picture.get("dueAmount", 0)),
            "instance": int(order_picture.get("instance", 0)),
            "lastModifiedLine": int(order_picture.get("lastModifiedLine", 0)),
            "lineCounter": int(order_picture.get("lineCounter", 0)),
            "orderDiscountAmount": float(order_picture.get("orderDiscountAmount", 0)),
            "originatorId": str(int(order_picture.get("originatorId")[-2:])),
            "podType": order_picture.get("podType"),
            "posId": self._get_pos_code(
                pos_id=order_picture.get("posId"),
                instance=order_picture.get("instance"),
                session_id=session_id,
            ),
            "priceBasis": order_picture.get("priceBasis"),
            "priceList": order_picture.get("priceList"),
            "saleType": sale_type if sale_type >= 0 else 0,
            "saleTypeDescr": order_picture.get("saleTypeDescr"),
            "sessionId": session_id,
            "stateId": int(order_picture.get("stateId")),
            "state": order_picture.get("stateDescr"),
            "subType": order_picture.get("subType"),
            "taxTotal": float(order_picture.get("taxTotal", 0)),
            "tip": float(order_picture.get("tip", 0)),
            "totalAfterDiscount": float(order_picture.get("totalAfterDiscount", 0)),
            "totalAmount": float(order_picture.get("totalAmount", 0)),
            "totalGross": float(order_picture.get("totalGross", 0)),
            "totalTender": float(order_picture.get("totalTender", 0)),
            "type": order_picture.get("type"),
            "typeId": int(order_picture.get("typeId")),
            "userId": self._get_pos_user_id(session_id=session_id),
            "CustomOrderProperties": self._get_custom_properties(
                order_picture=order_picture,
            ),
            "SaleLine": self._get_sale_lines(order_picture=order_picture),
            "StateHistory": self._get_state_history(order_picture=order_picture),
            "TenderHistory": self._get_order_tenders(order_picture=order_picture),
            "xml": self._get_fiscal_xml(
                order_picture=order_picture,
                fiscal_type="FISCAL_XML",
            ),
            "xmlCancel": self._get_fiscal_xml(
                order_picture=order_picture,
                fiscal_type="CANCELED_FISCAL_XML",
            ),
        }

        return order_picture_dict

    @staticmethod
    def _get_fiscal_xml(
        order_picture,  # type: eTree.Element
        fiscal_type,    # type: str
    ):
        # type: (...) -> Optional[str]

        fiscal_xml = None
        prop = order_picture.find(".//OrderProperty[@key='{}']".format(fiscal_type))
        if prop is not None:
            fiscal_xml = prop.get("value")

        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_order_picture_property(
        order_picture,      # type: eTree.Element
        order_property,     # type: str
    ):
        # type: (...) -> Optional[str]

        return order_picture.get("{}".format(order_property))

    @staticmethod
    def _get_business_period(
        order_picture,  # type: eTree.Element
    ):
        # type: (...) -> str

        order_business_period = order_picture.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
    ):
        # type: (...) -> 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(
        pos_id,         # type: str
        instance,       # type: str
        session_id,     # type: str
    ):
        # type: (...) -> str

        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 = instance

        return pos_id

    @staticmethod
    def _get_custom_properties(
        order_picture,      # type: eTree.Element
    ):
        # type: (...) -> Dict

        payload_custom_order_properties = {}

        custom_properties = order_picture.find("CustomOrderProperties") or []
        for custom_property in custom_properties:
            if custom_property.get("key").upper() not in ["FISCAL_XML", "CANCELED_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

    @staticmethod
    def _get_order_tenders(
        order_picture,  # type: eTree.Element
    ):
        # type: (...) -> List

        payload_order_tenders = []
        for tender_line in order_picture.findall("TenderHistory/Tender"):
            tender_payload = {
                "tenderId": tender_line.get("tenderId"),
                "tenderAmount": float(tender_line.get("tenderAmount", 0)),
                "tenderDescr": tender_line.get("TenderDescr"),
                "tenderType": tender_line.get("tenderType"),
                "changeAmount": float(tender_line.get("change", 0)),
                "tenderDetail": get_tender_detail(tender_line=tender_line),
                "tenderTip": float(tender_line.get("tip")) if tender_line.get("tip") else None
            }
            payload_order_tenders.append(tender_payload)

        return payload_order_tenders

    def _get_sale_lines(
        self,
        order_picture,  # type: eTree.Element
    ):
        # type: (...) -> List[Dict[str, Any[str, int]]]

        payload_order_sale_lines = []
        for sale_line in order_picture.findall("SaleLine"):
            sale_line_payload = {
                "addedUnitPrice": float(sale_line.get("addedUnitPrice", 0)),
                "decQty": float(sale_line.get("decQty", 0)),
                "incQty": float(sale_line.get("incQty", 0)),
                "itemId": sale_line.get("itemId"),
                "itemPrice": float(sale_line.get("itemPrice", 0)),
                "itemType": sale_line.get("itemType"),
                "lastUpdated": sale_line.get("lastUpdated"),
                "level": int(sale_line.get("level")),
                "lineNumber": int(sale_line.get("lineNumber")),
                "measureUnit": sale_line.get("measureUnit"),
                "multipliedQty": float(sale_line.get("multipliedQty") or sale_line.get("qty") or 0),
                "partCode": sale_line.get("partCode"),
                "priceKey": sale_line.get("priceKey"),
                "productName": sale_line.get("productName").encode("utf-8"),
                "productPriority": int(sale_line.get("productPriority")),
                "qty": float(sale_line.get("qty", 0)),
                "unitPrice": float(sale_line.get("unitPrice", 0)),
                "defaultQty": float(sale_line.get("defaultQty")) if sale_line.get("defaultQty") else None,
                "addedQty": float(sale_line.get("addedQty")) if sale_line.get("addedQty") else None,
                "subQty": float(sale_line.get("subQty")) if sale_line.get("subQty") else None,
                "chosenQty": float(sale_line.get("chosenQty")) if sale_line.get("chosenQty") else None,
                "discountsApplied": sale_line.get("discountsApplied"),
                "itemDiscount": float(sale_line.get("itemDiscount", 0)),
                "custom-properties": self._get_sale_line_custom_properties(sale_line=sale_line),
                "taxItem": self._get_sale_line_tax_items(sale_line=sale_line),
                "comment": self._get_sale_line_comments(sale_line=sale_line),
                "subUnitPrice": float(sale_line.get("subUnitPrice", 0)),
                "tags": self._get_sale_line_tags(sale_line=sale_line),
            }
            payload_order_sale_lines.append(sale_line_payload)

        return payload_order_sale_lines

    @staticmethod
    def _get_state_history(
        order_picture,  # type: eTree.Element
    ):
        # type: (...) -> JSON

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

    @staticmethod
    def _get_sale_line_comments(
        sale_line,  # type: eTree.Element
    ):
        # type: (...) -> 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.Element
    ):
        # type: (...) -> JSON

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

    @staticmethod
    def _get_sale_line_tax_items(
        sale_line,  # type: eTree.Element
    ):
        # type: (...) -> JSON

        tax_items = []
        for order_tax_item in sale_line.findall("TaxItem"):
            tax_item = {
                "baseAmountAD": float(order_tax_item.get("baseAmountAD", 0)),
                "baseAmountBD": float(order_tax_item.get("baseAmountBD", 0)),
                "taxAmountAD": float(order_tax_item.get("taxAmountAD", 0)),
                "taxAmountBD": float(order_tax_item.get("taxAmountBD", 0)),
                "taxIndex": order_tax_item.get("taxIndex"),
                "taxRate": float(order_tax_item.get("taxRate", 0)),
                "taxRuleId": int(order_tax_item.get("taxRuleId"))
            }
            tax_items.append(tax_item)

        return tax_items

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

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

        return item_tags
