from xml.etree import cElementTree as eTree
from typing import (
    Any,
    List,
    Optional,
    Union
)

from application.domain import TableService, Table, TablePictureToOrdersParser
from pos_model import TableStatus
from sysactions import get_model, get_posot, get_poslist
from tablemgrapi import get_pos_ts

from application.interactor import TableInteractor
from model.customexception import GettingUserInfoException
from utilfunctions import get_custom_property_value


class MsgBusTableService(TableService):
    def get_selected_table(self, pos_id, tables=None):
        if tables is None:
            tables = self.list_tables(pos_id)
        selected_tables = filter(lambda x: x.current_pos_id == pos_id, tables)

        selected_table = selected_tables[0] if len(selected_tables) > 0 else None
        if selected_table is not None:
            table_is_linked = selected_table.status == 5
            if table_is_linked:
                self.store_service(pos_id, selected_table.id)
                return None

        return selected_table

    def get_table_info(self, pos_id, table_id):
        pos_ts = _a_get_pos_ts(pos_id)
        return pos_ts.getTableInfo(table_id)

    def list_tables(
            self,
            pos_id  # type: str
    ):
        # type: (...) -> List[Table]

        pos_ts = _a_get_pos_ts(pos_id)

        tables_list = []
        for table in pos_ts.listTables(with_props=1):
            table_obj = Table()
            table_obj.id = table["id"]
            table_obj.name = table["name"] or table["id"]
            table_obj.current_pos_id = table["currentPOSId"]
            table_obj.status = int(table["status"])
            table_obj.slice_source_table_id = table.get("sliceSourceTableId", None)
            table_obj.slice_parent_table_id = table.get("sliceParentTableId", None)
            table_obj.tab_id = None
            if table["typeDescr"] == 'Tab' and table["properties"]:
                table_obj.tab_id = table.get("properties").get("TAB_ID")
            if table["properties"]:
                table_obj.mode = table.get("properties").get("MODE", None)
            table_obj.business_period = None
            if table["businessPeriod"]:
                table_obj.business_period = int(table["businessPeriod"].replace("-", ""))
            if table["sector"]:
                table_obj.sector = table["sector"]
            tables_list.append(table_obj)

        return tables_list

    def set_parent_table(
        self,
        pos_id,                 # type: str
        table_id,               # type: str
        parent_table_id=None    # type: Optional[str]
    ):
        # type: (...) -> None

        model = get_model(pos_id)
        pos_ts = get_pos_ts(model)
        user_id = self.get_user_id(model)
        pos_ts.setParentTable(
            user_id,
            table_id,
            parent_table_id
        )

    def set_current_order(
        self,
        pos_id,     # type: str
        order_id,   # type: str
        table_id    # type: str
    ):
        # type (...) -> None

        model = get_model(pos_id)
        pos_ts = get_pos_ts(model)
        user_id = self.get_user_id(model)

        if table_id is None:
            table_id = self.get_selected_table(pos_id).id

        tab = self._get_related_tab(pos_id, table_id, order_id)
        table_id = tab if tab else table_id

        pos_ts.setCurrentOrder(user_id, table_id, order_id)

    def _get_related_tab(
            self,
            pos_id,      # type: str
            table_id,    # type: str
            order_id     # type: str
    ):
        # type: (...) -> Optional[str]

        is_tab_mode = self.get_current_table_mode(pos_id, table_id) == 'tabs'
        tab = get_custom_property_value(pos_id, 'TABLE_ID', order_id)

        if is_tab_mode and tab.startswith('TAB'):
            table_interactor = TableInteractor(
                pos_id=pos_id,
                table_id=table_id,
                table_service=self)
            table_interactor.change_selected_table(
                pos_id=pos_id,
                selected_table_id=tab
            )
            return tab

    def void_service_order(self, pos_id, table_id, order_id):
        model = get_model(pos_id)
        pos_ts = get_pos_ts(model)
        user_id = self.get_user_id(model, False)

        pos_ts.voidServiceOrder(user_id, table_id, order_id)

    def start_service(self, pos_id, table_id="", seats="", tab_id="", mode="", source_table_id=""):
        # type: (Union[int, str], str, str, str, str, str) -> str

        model = get_model(pos_id)
        pos_ts = get_pos_ts(model)
        self.tab_id_is_valid(pos_id, tab_id, pos_ts)

        business_period = model.find("PosState").get("period")
        business_period = business_period[0:4] + "-" + business_period[4:6] + "-" + business_period[6:8]
        user_id = self.get_user_id(model)
        custom_properties = {"MODE": mode}
        if tab_id != "":
            custom_properties["TAB_ID"] = tab_id
        seats = '1' if seats is None else seats

        response = pos_ts.startService(user_id, business_period, table_id, seats, custom_properties, source_table_id)
        return response

    def tab_id_is_valid(self, pos_id, tab_id, pos_ts=None):
        if not pos_ts:
            pos_ts = get_pos_ts(get_model(pos_id))

        if tab_id != '':
            for table in pos_ts.listTables(with_props=1):
                if str(table["id"]).startswith("TAB") \
                        and int(table['status']) != TableStatus.CLOSED \
                        and table["properties"] is not None \
                        and "TAB_ID" in table["properties"] \
                        and table["properties"]["TAB_ID"] == tab_id:
                    raise Exception("TAB_ALREADY_EXISTS")

    def is_a_tab(self, pos_id, table_id, table_picture=None):
        if not table_picture:
            table_picture = eTree.XML(self.get_table_info(pos_id, table_id))

        if type(table_picture) in [str, unicode]:
            table_picture = eTree.XML(table_picture)

        if table_picture.get("id").startswith("TAB"):
            return True
        return False

    def recall_service(self, pos_id, table_id):
        model = get_model(pos_id)
        pos_ts = get_pos_ts(model)
        user_id = self.get_user_id(model)

        return pos_ts.recallService(user_id, table_id)

    def store_service(self, pos_id, table_id):
        model = get_model(pos_id)
        pos_ts = get_pos_ts(model)
        user_id = self.get_user_id(model, False)

        return pos_ts.storeService(user_id, table_id)

    def create_order(self, pos_id, table_id, price_list=''):
        model = get_model(pos_id)
        pos_ts = get_pos_ts(model)
        user_id = self.get_user_id(model)

        return pos_ts.createOrder(user_id, table_id, price_list)

    def slice_service(self, pos_id, new_setup, src_table_id):
        model = get_model(pos_id)
        pos_ts = get_pos_ts(model)
        user_id = self.get_user_id(model)

        return pos_ts.sliceService(user_id, new_setup, src_table_id)

    def get_table_picture(self, pos_id, table_id):
        pos_ts = _a_get_pos_ts(pos_id)
        return pos_ts.getTablePicture(table_id)

    def get_current_pos_id(self, pos_id, table_id):
        model = get_model(pos_id)
        table = model.find('STORE_MODIFIED/Tables/Table[@id="{}"]'.format(table_id))
        return table.get("currentPOSId")

    def do_total_table(
        self,
        pos_id,                     # type: str
        table_id,                   # type: str
        tip_rate="",                # type: Optional[str]
        selected_to_total=False     # type: Optional[bool]
    ):
        # type: (...) -> str

        table_to_total_id = table_id

        if selected_to_total:
            self.set_table_custom_property(
                pos_id,
                "SELECTED_TO_TOTAL",
                selected_to_total,
                table_id
            )
            if selected_to_total != "all_services":
                table_to_total_id = selected_to_total

        model = get_model(pos_id)
        user_id = self.get_user_id(model)

        pos_ts = get_pos_ts(model)
        return pos_ts.totalService(
            user_id=user_id,
            table_id=table_to_total_id,
            tip_rate=tip_rate
        )

    def reopen_table(self, pos_id, table_id):
        model = get_model(pos_id)
        pos_ts = get_pos_ts(model)
        user_id = self.get_user_id(model, False)

        return pos_ts.reopenService(user_id, table_id)

    def close_table(self, pos_id, table_id):
        model = get_model(pos_id)
        pos_ts = get_pos_ts(model)
        user_id = self.get_user_id(model)

        return pos_ts.closeService(user_id, table_id)

    def set_table_available(self, pos_id, table_id):
        model = get_model(pos_id)
        pos_ts = get_pos_ts(model)
        user_id = self.get_user_id(model, False)

        pos_ts.tableReady4Service(user_id, table_id)

    def get_current_table_id(self, pos_id):
        try:
            model = get_model(pos_id)
            table = model.find('STORE_MODIFIED/Tables/Table[@currentPOSId="{}"]'.format(pos_id))
            return table.get("id")
        except Exception as _: # noqa
            return None

    def clear_service_tenders(self, pos_id, table_id):
        model = get_model(pos_id)
        pos_ts = get_pos_ts(model)
        user_id = self.get_user_id(model, False)

        return pos_ts.clearServiceTenders(user_id, table_id)

    def change_service_tip(self, pos_id, table_id, percentage=None, amount=None):
        model = get_model(pos_id)
        pos_ts = get_pos_ts(model)
        user_id = self.get_user_id(model)

        return pos_ts.changeServiceTip(user_id, table_id, percentage, amount)

    def set_table_custom_property(self, pos_id, prop, prop_value=None, table_id=None, service_id=None):
        model = get_model(pos_id)
        pos_ts = get_pos_ts(model)
        pos_ts.setCustomProperty(prop, prop_value, table_id, service_id)

    def get_table_custom_property(self, pos_id, prop, table_id=None, service_id=None):
        model = get_model(pos_id)
        pos_ts = get_pos_ts(model)
        return pos_ts.getCustomProperty(prop, table_id, service_id)

    def register_service_tender(self, pos_id, table_id, tender_id, amount, tender_details=""):
        model = get_model(pos_id)
        pos_ts = get_pos_ts(model)
        user_id = self.get_user_id(model)

        return pos_ts.registerServiceTender(
            user_id,
            table_id,
            tender_id,
            str(amount),
            tender_detail=tender_details)

    def move_child_table(
        self,
        pos_id,                 # type: Any[Union[int, str]]
        source_table_id,        # type: str
        destination_table_id,   # type: str
        child_table_id,         # type: str
        mode                    # type: str
    ):

        model = get_model(pos_id)
        pos_ts = get_pos_ts(model)
        user_id = self.get_user_id(model)
        custom_properties = {"MODE": mode}
        pos_ts.moveChildTable(
            user_id=user_id,
            from_table_id=source_table_id,
            dest_table_id=destination_table_id,
            child_table_id=child_table_id,
            custom_props=custom_properties
        )

    def get_table_orders(self, pos_id, table_id, table_picture=None):
        if table_picture is None:
            table_picture = self.get_table_picture(pos_id, table_id)
        orders = TablePictureToOrdersParser.parse(table_picture)

        return orders

    def get_service_seats_count(self, table_info_xml):
        service_seats = int(table_info_xml.get("serviceSeats"))
        return int(service_seats)

    def set_sale_line_seat(self, pos_id, seat_number, order_id, line_number, item_id, level, part_code):
        self._set_order_custom_property(pos_id, 'seat', seat_number, order_id, line_number, item_id, level, part_code)

    def get_current_table_mode(
            self,
            pos_id,     # type: str
            table_id    # type: str
    ):
        # type: (...) -> str

        table_picture = eTree.XML(self.get_table_picture(
            pos_id=pos_id,
            table_id=table_id
        ))[0]
        return table_picture.get('MODE')

    @staticmethod
    def get_exclusive_pos_list(user_control_type=None, pos_list=None):
        if not pos_list:
            pos_list = get_poslist()

        pos_list = list(pos_list)

        if not user_control_type:
            return sorted(pos_list)

        for pos_id in sorted(pos_list):
            model = get_model(pos_id)
            working_mode = model.find('.//WorkingMode').get('usrCtrlType')
            if working_mode != user_control_type:
                pos_list.remove(pos_id)
        return sorted(pos_list)

    @staticmethod
    def _set_order_custom_property(pos_id, key, value, order_id, *params):
        model = get_model(pos_id)
        pos_ot = get_posot(model)

        if order_id:
            pos_ot.setOrderCustomProperty(key, value, order_id, *params)
        else:
            pos_ot.setOrderCustomProperty(key, value, '', *params)

    @staticmethod
    def get_user_id(model, mandatory=True):
        try:
            user_id = model.find("Operator").get("id")
        except Exception as ex: # noqa
            if mandatory is False:
                return '0'

            raise GettingUserInfoException(ex.args)

        return user_id


def _a_get_pos_ts(pos_id):
    model = get_model(pos_id)
    return get_pos_ts(model)
