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

import iso8601
from order_api.dto import (
    Order,
    parse_order,
)
from ._ServiceChild import ServiceChild
from ._SimpleTable import SimpleTable
from ._TableMode import TableMode
from ._TableStatus import TableStatus


class TableServiceDto(object):
    def __init__(
        self,                       # type: TableServiceDto
        table,                      # type: SimpleTable
        service_id,                 # type: int
        mode,                       # type: TableMode
        started_at,                 # type: datetime
        business_period,            # type: date
        user_id,                    # type: int
        number_of_seats,            # type: Optional[int]
        custom_properties=None,     # type: Optional[Dict[str, str]]
        parent_table_id=None,       # type: Optional[str]
        source_table_id=None,       # type: Optional[str]
        service_orders=None,        # type: Optional[List[Order]]
        payment_order=None,         # type: Optional[Order]
        service_children=None,      # type: Optional[List[ServiceChild]]
    ):
        self.table = table
        self.service_id = service_id
        self.mode = mode
        self.status = table.status
        self.started_at = started_at
        self.business_period = business_period
        self.user_id = user_id
        self.number_of_seats = number_of_seats
        self.custom_properties = custom_properties or {}
        self.parent_table_id = parent_table_id
        self.source_table_id = source_table_id
        self.service_orders = service_orders or []
        self.payment_order = payment_order or []
        self.service_children = service_children or []

        self.updated_at = started_at

    def to_xml(self):
        table_attrib = self.table.get_table_xml_attrib()
        table_attrib.update({
            "sector": self.table.sector or "",
            "serviceId": str(self.service_id),
            "businessPeriod": self.business_period.strftime("%Y-%m-%d"),
            "userId": str(self.user_id),
            "startTS": self.started_at.strftime("%Y-%m-%d %H:%M:%S"),
            "lastUpdateTS": self.updated_at.strftime("%Y-%m-%d %H:%M:%S"),
            "mode": str(self.mode.value),
        })
        if self.number_of_seats is not None:
            table_attrib["serviceSeats"] = str(self.number_of_seats)

        table = ElementTree.Element(
            "Table",
            attrib=table_attrib,
        )

        if self.parent_table_id is not None:
            table.attrib["sliceParentTableId"] = str(self.parent_table_id)

        if self.source_table_id is not None:
            table.attrib["sliceSourceTableId"] = str(self.source_table_id)

        if len(self.service_orders) > 0:
            table.attrib["orders"] = ",".join(map(lambda x: str(x.order_id), self.service_orders))

            service_orders_xml = ElementTree.Element("ServiceOrders")
            orders_xml = ElementTree.Element("Orders")
            for service_order in self.service_orders:
                service_order_xml = ElementTree.Element(
                    "ServiceOrder",
                    attrib={
                        "orderId": str(service_order.order_id),
                        "state": service_order.state.name,
                        "tableServiceId": str(self.service_id),
                        "createdAt": service_order.created_at.strftime("%Y-%m-%dT%H:%M:%S.%f"),
                    }
                )
                service_orders_xml.append(service_order_xml)
                orders_xml.append(service_order.to_xml())
            table.append(service_orders_xml)
            table.append(orders_xml)

        if self.table.status == TableStatus.Totaled and self.payment_order:
            table.attrib["orders"] = str(self.payment_order.order_id)

            payment_orders_xml = ElementTree.Element("PaymentOrders")

            orders_xml = ElementTree.Element("Orders")
            payment_order_xml = ElementTree.Element(
                "PaymentOrder",
                attrib={
                    "orderId": str(self.payment_order.order_id),
                    "state": self.payment_order.state.name,
                    "createdAt": self.payment_order.created_at.strftime("%Y-%m-%dT%H:%M:%S.%f"),
                }
            )
            payment_orders_xml.append(payment_order_xml)
            orders_xml.append(self.payment_order.to_xml())

            table.append(payment_orders_xml)
            table.append(orders_xml)

        if self.service_children:
            service_children_xml = ElementTree.Element("ServiceChildren")
            for service_child in self.service_children:
                service_child_xml = ElementTree.Element(
                    "ServiceChild",
                    attrib={
                        "serviceId": str(service_child.service_id),
                        "tableId": service_child.table_id,
                        "status": str(service_child.status),
                        "tableName": service_child.table_name,
                    }
                )
                service_children_xml.append(service_child_xml)
            table.append(service_children_xml)

        if self.table.pos_id is not None:
            table.attrib["currentPOSId"] = str(self.table.pos_id)

        to_export_properties = {
            "MODE": "seats" if self.mode == TableMode.SEAT else "tabs",
        }
        for key, value in self.custom_properties.items():
            to_export_properties[key] = str(value)

        properties = ElementTree.Element(
            "Properties",
            attrib=to_export_properties,
        )
        table.append(properties)

        return table

    @staticmethod
    def from_xml(
        xml,        # type: ElementTree.Element
    ):
        # type: (...) -> TableServiceDto

        custom_properties = {}
        xml_properties = xml.find("Properties")
        for key, value in xml_properties.attrib.items():
            custom_properties[key] = value

        service_children = []
        for service_child_xml in xml.findall("ServiceChildren/ServiceChild"):
            service_children.append(ServiceChild(
                service_id=int(service_child_xml.attrib["serviceId"]),
                table_id=service_child_xml.attrib["tableId"],
                status=int(service_child_xml.attrib["status"]),
                table_name=service_child_xml.attrib["tableName"],
            ))

        service_orders = []
        service_order_ids = set()
        for order_xml in xml.findall("ServiceOrders/ServiceOrder"):
            service_order_ids.add(int(order_xml.attrib["orderId"]))

        payment_order = None
        payment_order_id = None
        for order_xml in xml.findall("PaymentOrders/PaymentOrder"):
            payment_order_id = int(order_xml.attrib["orderId"])

        for order_xml in xml.findall("Orders/Order"):
            order_id = int(order_xml.attrib["orderId"])
            if order_id in service_order_ids:
                service_orders.append(parse_order(order_xml))
            if order_id == payment_order_id:
                payment_order = parse_order(order_xml=order_xml)

        table = SimpleTable.from_xml(xml=xml)

        parent_table_id = xml.get("sliceParentTableId", None)
        source_table_id = xml.get("sliceSourceTableId", None)

        return TableServiceDto(
            table=table,
            service_id=int(xml.attrib["serviceId"]),
            mode=TableMode(int(xml.attrib["mode"])),
            started_at=iso8601.parse_date(xml.attrib["startTS"]),
            business_period=iso8601.parse_date(xml.attrib["businessPeriod"]).date(),
            user_id=int(xml.attrib["userId"]),
            number_of_seats=int(xml.attrib.get("serviceSeats")) if xml.get("serviceSeats") else None,
            custom_properties=custom_properties,
            service_children=service_children,
            service_orders=service_orders,
            payment_order=payment_order,
            parent_table_id=parent_table_id,
            source_table_id=source_table_id,
        )

    def get_tab_id(
        self,   # type: TableServiceDto
    ):
        # type: (...) -> str

        return "TAB-{}".format(self.table.table_id) if self.table.is_tab else None
