import json
from dataclasses import dataclass
from typing import (
    Any,
    Dict,
    List,
    Optional,
)
from xml.etree import cElementTree as eTree

import iso8601
from nreports.model import ProductTypeName
from nreports.repository import ProductRepository


@dataclass
class Tender:
    value: str = ""
    tender_type: str = ""
    tender_type_description: str = ""
    tender_change_amount: str = ""


@dataclass
class Item:
    part_code: Optional[int] = None
    product_name: str = ""
    item_type: Optional[str] = None
    quantity: Optional[int] = None
    price: Optional[float] = None
    observation: Optional[str] = None
    is_cfh: bool = False


class DeliveryOrderDto:

    def __init__(
        self,
        order_picture_xml: eTree.Element,
        delivery_json: Dict[str, Any],
        product_repository: ProductRepository,
    ) -> None:

        self.product_repository = product_repository
        self.order_number = delivery_json.get("shortReference", "0")
        self.order_date = iso8601.parse_date(delivery_json.get("createAt", "2099-01-01T00:00:00.00000Z"))
        self.items = self._get_items(order_items=delivery_json.get("items", []))
        self.total_price = delivery_json.get("totalPrice", "0.00")
        self.sub_total_price = delivery_json.get("subTotal", "0.00")
        self.delivery_fee = float(delivery_json.get("deliveryFee", "0.00"))
        self.observation = self._get_custom_property(
            delivery_json=delivery_json,
            key="observation",
        )
        self.observations = delivery_json.get("observations", "")
        self.tenders = self._build_tenders(
            order_picture_xml=order_picture_xml,
            delivery_json=delivery_json,
        )
        self.customer_name = self._get_custom_property(
            delivery_json=delivery_json,
            key="customer_name",
        )
        self.customer_doc = self._get_custom_property(
            delivery_json=delivery_json,
            key="customer_doc",
        )
        self.customer_phone = self._get_custom_property(
            delivery_json=delivery_json,
            key="customer_phone",
        )
        phone_localizer = self._get_custom_property(
            delivery_json=delivery_json,
            key="customer_phone_localizer",
        )
        if phone_localizer:
            self.customer_phone += " ID: {}".format(phone_localizer)

        self.additional_fee = float(
            self._get_custom_property(
                delivery_json=delivery_json,
                key="additional_fee"
            ) or 0
        )
        self.pos_order_id = order_picture_xml.get("orderId")[-4:]
        self.partner = delivery_json["partner"]
        self.display_partner = self._get_custom_property(
            delivery_json=delivery_json,
            key="display_partner",
        )
        self.need_logistics = delivery_json.get("needLogistics", False)

        address = delivery_json.get("pickup", {}).get("address") or {}

        self.address = address.get("formattedAddress")
        self.address_neighborhood = address.get("neighborhood")
        self.city = address.get("city")
        self.address_complement = address.get("complement")
        self.postal_code = address.get("postalCode")
        self.reference = address.get("reference")
        self.take_out_order = delivery_json.get("pickup", {}).get("type", "").lower() == "take_out"

        self.vouchers = {}
        self._create_vouchers(delivery_json=delivery_json)

    def _get_items(
        self,
        order_items: List[Dict[str, Any]],
        father_qty: int = 1,
        level: int = 0,
    ) -> List[Item]:

        items = []
        for order_item in order_items:
            item = self._convert_order_item_into_item(order_item=order_item)

            if item.product_name is None:
                continue

            if item.quantity is not None:
                item.quantity *= father_qty

            if item.item_type != ProductTypeName.OPTION and not item.is_cfh:
                item.product_name = "  " * level + item.product_name
                items.append(item)

                if item.observation:
                    items.append(Item(product_name=item.observation))

            if "parts" in order_item:
                current_level = level if item.item_type == ProductTypeName.OPTION else level + 1
                items.extend(
                    self._get_items(
                        order_items=order_item.get("parts", []),
                        father_qty=item.quantity,
                        level=current_level,
                    )
                )

        return items

    def _convert_order_item_into_item(
        self,
        order_item: Dict[str, Any],
    ) -> Item:

        part_code = int(order_item.get("partCode"))
        observation = order_item.get("observation") or ""
        product_data = self.product_repository.get_product_data(product_id=part_code)
        return Item(
            part_code=part_code,
            product_name=product_data.product_name if product_data else None,
            item_type=order_item.get("itemType") or (product_data.product_type if product_data else ""),
            quantity=order_item.get("quantity"),
            price=order_item.get("price"),
            observation="   " + observation.replace("\n", " ") if observation else None,
            is_cfh=product_data.product_cfh if product_data else False,
        )

    def _create_vouchers(
        self,
        delivery_json: Dict[str, Any],
    ) -> None:

        self.vouchers["ORDER"] = 0
        self.vouchers["TOTAL_ITEMS"] = 0
        self.vouchers["DELIVERY_FEE"] = 0

        if "vouchers" in delivery_json and "store" in delivery_json["vouchers"]:
            self._get_order_vouchers(delivery_json=delivery_json)
            self._get_total_items_vouchers(delivery_json=delivery_json)
            self._get_delivery_fee_vouchers(delivery_json=delivery_json)

    def _get_order_vouchers(
        self,
        delivery_json: Dict[str, Any],
    ) -> None:

        if "order" in delivery_json["vouchers"]["store"]:
            self.vouchers["ORDER"] += delivery_json["vouchers"]["store"]["order"]

        if "order" in delivery_json["vouchers"]["partner"]:
            self.vouchers["ORDER"] += delivery_json["vouchers"]["partner"]["order"]

    def _get_total_items_vouchers(
        self,
        delivery_json: Dict[str, Any],
    ) -> None:

        if "total_items" in delivery_json["vouchers"]["store"]:
            self.vouchers["TOTAL_ITEMS"] += delivery_json["vouchers"]["store"]["total_items"]

        if "total_items" in delivery_json["vouchers"]["partner"]:
            self.vouchers["TOTAL_ITEMS"] += delivery_json["vouchers"]["partner"]["total_items"]

    def _get_delivery_fee_vouchers(
        self,
        delivery_json: Dict[str, Any],
    ) -> None:

        if "delivery_fee" in delivery_json["vouchers"]["store"]:
            self.vouchers["DELIVERY_FEE"] += delivery_json["vouchers"]["store"]["delivery_fee"]

        if "delivery_fee" in delivery_json["vouchers"]["partner"]:
            self.vouchers["DELIVERY_FEE"] += delivery_json["vouchers"]["partner"]["delivery_fee"]

    @staticmethod
    def _get_custom_property(
        delivery_json: Dict[str, Any],
        key: str,
    ) -> str:

        if "custom_properties" in delivery_json:
            value = [x for x in delivery_json["custom_properties"] if x["key"].lower() == key.lower()]
            if len(value) > 0:
                return value[0]["value"]

        return ""

    def _build_tenders(
        self,
        order_picture_xml: eTree.Element,
        delivery_json: Dict[str, Any],
    ) -> List[Tender]:

        delivery_partner = self._get_custom_property(
            delivery_json=delivery_json,
            key="partner",
        )
        if delivery_partner == "MANUAL":
            return self._get_tenders_manual_delivery_json(order_picture_xml=order_picture_xml)

        return self._get_tenders_from_delivery_json(delivery_json=delivery_json)

    @staticmethod
    def _get_tenders_manual_delivery_json(
        order_picture_xml: eTree.Element,
    ) -> List[Tender]:

        tender_list = []
        for tender_element in order_picture_xml.findall(".//TenderHistory/Tender"):
            if float(tender_element.get("tenderAmount")) <= 0:
                continue

            tender_detail = tender_element.get("tenderDetail")
            tender_detail_json = json.loads(tender_detail) if tender_detail else {}
            tender_data = Tender(
                value=tender_element.get("tenderAmount", ""),
                tender_type=tender_element.get("tenderDescr", ""),
                tender_type_description=tender_detail_json.get("tenderTypeDescription", ""),
                tender_change_amount=tender_detail_json.get("tenderChangeAmount", "")
            )
            tender_list.append(tender_data)

        return tender_list

    @staticmethod
    def _get_tenders_from_delivery_json(
        delivery_json: Dict[str, Any],
    ) -> List[Tender]:

        tenders = delivery_json.get("tenders", [])
        tender_list = []
        for tender in tenders:
            if float(tender.get("value", 0)) <= 0:
                continue

            tender_data = Tender(
                value=tender.get("value", ""),
                tender_type=tender.get("type", ""),
            )
            tender_list.append(tender_data)

        return tender_list
