from datetime import datetime
from typing import (
    List,
    Dict,
    Optional,
)
from xml.etree.ElementTree import Element

import iso8601
from ._OrderFee import Fees
from ._SaleType import SaleType
from ._CustomProperty import CustomProperty
from ._OrderState import OrderState
from ._SaleLine import SaleLine
from ._StateHistory import StateHistory
from ._Tender import Tender
from ._Traits import Traits
from ._PackageState import PackageState


class Order(object):
    def __init__(
        self,                       # type: Order
        order_id,                   # type: int
        pos_id,                     # type: int
        session_id,                 # type: str
        sale_type,                  # type: SaleType
        state_history,              # type: List[StateHistory]
        traits,                     # type: Traits
        total_amount=None,          # type: Optional[float]
        total_gross=None,           # type: Optional[float]
        discount_amount=None,       # type: Optional[float]
        total_tender=None,          # type: Optional[float]
        total_after_discount=None,  # type: Optional[float]
        due_amount=None,            # type: Optional[float]
        change=None,                # type: Optional[float]
        tip=None,                   # type: Optional[float]
        tax_total=None,             # type: Optional[float]
        sale_lines=None,            # type: Optional[List[SaleLine]]
        custom_properties=None,     # type: Optional[List[CustomProperty]]
        tenders=None,               # type: Optional[List[Tender]]
        business_period=None,       # type: Optional[str]
        order_xml=None,             # type: Element
        fees=None,                  # type: Optional[Fees]
    ):
        # type: (...) -> None

        self.order_id = order_id
        self.pos_id = pos_id
        self.session_id = session_id
        self.sale_type = sale_type
        self.state_history = state_history
        self.traits = traits
        self.total_amount = total_amount
        self.total_gross = total_gross
        self.discount_amount = discount_amount
        self.total_tender = total_tender
        self.total_after_discount = total_after_discount
        self.due_amount = due_amount
        self.change = change
        self.tip = tip
        self.tax_total = tax_total
        self.total_gross = total_gross
        self.sale_lines = sale_lines or []
        self.custom_properties = custom_properties or []
        self.tenders = tenders or []
        self.business_period = business_period
        self.order_xml = order_xml
        self.fees = fees or []

    def get_custom_property(
        self,   # type: Order
        key,    # type: str
    ):
        # type: (...) -> Optional[CustomProperty]

        for custom_property in self.custom_properties:
            if custom_property.key == key:
                return custom_property

        return None

    @property
    def state(self):
        # type: (...) -> OrderState
        return self.state_history[-1].state

    @property
    def created_at(
        self,   # type: Order
    ):
        # type: (...) -> datetime

        return self.state_history[0].created_at

    @property
    def created_at_localtime(
        self,   # type: Order
    ):
        # type: (...) -> datetime

        return self.state_history[0].created_at_localtime

    @property
    def paid_at(
        self,   # type: Order
    ):
        # type: (...) -> Optional[datetime]

        if not self.was_paid:
            return None

        state_history = self._get_lastest_state_history(order_state=OrderState.PAID)
        return state_history.created_at if state_history else None

    @property
    def paid_at_localtime(
        self,   # type: Order
    ):
        # type: (...) -> Optional[datetime]

        if not self.was_paid:
            return None

        state_history = self._get_lastest_state_history(order_state=OrderState.PAID)
        return state_history.created_at_localtime if state_history else None

    def _get_lastest_state_history(
        self,           # type: Order
        order_state,    # type: OrderState
    ):
        # type: (...) -> Optional[StateHistory]

        for state_history in reversed(self.state_history):
            if state_history.state == order_state:
                return state_history

        return None

    def to_xml(self):
        # type: (...) -> Element
        return self.order_xml

    @property
    def is_from_table(self):
        # type: (...) -> bool

        return self.table_id is not None

    @property
    def table_id(self):
        # type: (...) -> Optional[str]

        prop = self.get_custom_property(key="TABLE_ID")
        if prop is None:
            return None

        return prop.value

    def has_state(
        self,  # type: Order
        state,  # type: OrderState
    ):
        for order_state in self.state_history:
            if order_state.state == state:
                return True

        return False

    @property
    def was_stored(self):
        # type: (...) -> bool

        return self.has_state(state=OrderState.STORED)

    @property
    def was_paid(self):
        # type: (...) -> bool

        return self.has_state(state=OrderState.PAID)

    @property
    def is_voided(self):
        # type: (...) -> bool

        return self.state == OrderState.VOIDED

    @property
    def is_paid(self):
        # type: (...) -> bool

        return self.state == OrderState.PAID

    @property
    def is_canceled(self):
        # type: (...) -> bool

        return self.state in (OrderState.VOIDED, OrderState.SYSTEM_VOIDED, OrderState.ABANDONED)

    @property
    def fiscalization_date(self):
        # type: (...) -> Optional[datetime]

        fiscalization_date = self.get_custom_property(key="FISCALIZATION_DATE")
        if fiscalization_date is None:
            return None

        return iso8601.parse_date(fiscalization_date.value)

    @property
    def grand_total(self):
        # type: (...) -> float

        return self.total_gross + self.tip

    @property
    def sub_total(self):
        # type: (...) -> float

        return self.grand_total + self.discount_amount

    @property
    def discount_percentage(self):
        # type: (...) -> float

        if self.grand_total == 0:
            return 0.0

        return self.discount_amount / self.grand_total

    @property
    def operator_id(
        self,  # type: Order
    ):
        # type: (...) -> int

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

        return -1

    @staticmethod
    def get_package_state(
        sale_type,             # type: str
        custom_properties,     # type: Dict[str, str]
        state,                 # type: str
    ):
        # type: (...) -> PackageState

        if int(sale_type) != SaleType.PACKAGE.value:
            raise ValueError("Order is not a package")

        if custom_properties.get('FISCALIZATION_DATE') is not None:
            return PackageState.FINALIZED

        elif state == OrderState.PAID.name:
            return PackageState.PAID

        elif state == OrderState.VOIDED.name:
            return PackageState.VOIDED

        return PackageState.WAITING_PAYMENT
