from xml.etree import cElementTree as eTree

import sysactions
from order_api.dto import (
    CustomProperty,
    SaleLineIdentifier,
)
from pos_model import TableStatus
from sysactions import (
    UiOutOfSync,
    force_send_updated_model_to_ui,
    get_model,
    get_poslist,
)
from table_manager_api.dto import TableMode
from tablemgrapi import get_pos_ts
from typing import (
    Any,
    Dict,
    List,
    Optional,
    Union
)

from api.order import get_order_service
from api.table import get_table_service
from application.domain import TableService, Table, TablePictureToOrdersParser
from application.interactor import TableInteractor
from model.customexception import GettingUserInfoException
from thread_local import get_current_model, get_current_pos_id
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"]
            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"]

            if table["subSector"]:
                table_obj.sub_sector = table["subSector"]

            if table["userId"] and table_obj.status != TableStatus.AVAILABLE:
                table_obj.user_id = table["userId"]

            table_obj.sub_sector_name = self._get_table_sector_name(
                table=table_obj,
            )
            tables_list.append(table_obj)

        return tables_list

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

        pos_ts = _a_get_pos_ts(pos_id)
        user_id = self.get_user_id(pos_id)

        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

        pos_ts = _a_get_pos_ts(pos_id)
        user_id = self.get_user_id(pos_id)

        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):
        pos_ts = _a_get_pos_ts(pos_id)
        user_id = self.get_user_id(pos_id, False)

        pos_ts.voidServiceOrder(user_id, table_id, order_id)

    def start_service(
        self,                       # type: MsgBusTableService
        pos_id,                     # type: Union[int, str]
        table_id,                   # type: str
        seats,                      # type: int
        name="",                    # type: str
        mode="",                    # type: str
        source_table_id="",         # type: str
        custom_properties=None,     # type: Optional[Dict[str, str]]
    ):
        # type: (...) -> str

        seats = '1' if seats is None else seats
        table_service = get_table_service(pos_id=pos_id)

        if table_id:
            response = table_service.start_service(
                table_id=table_id,
                mode=TableMode.SEAT if mode == "seats" else TableMode.TABS,
                num_seats=int(seats),
                custom_properties=custom_properties,
            )
        else:
            parent_table_id = None
            if source_table_id != "":
                parent_table_id = int(source_table_id)
            response = table_service.start_tab(
                tab_name=name,
                parent_table_id=parent_table_id,
                custom_properties=custom_properties,
            )
        return eTree.tostring(response.to_xml())

    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]:  # noqa: F821
            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):
        pos_ts = _a_get_pos_ts(pos_id)
        user_id = self.get_user_id(pos_id)

        return pos_ts.recallService(user_id, table_id)

    def store_service(self, pos_id, table_id, order_id=None):
        pos_ts = _a_get_pos_ts(pos_id)
        user_id = self.get_user_id(pos_id, False)

        return pos_ts.storeService(user_id, table_id, order_id)

    def create_order(
        self,                   # type: MsgBusTableService
        pos_id,                 # type: Union[int, str]
        table_id,               # type: str
        price_list="",          # type: Optional[str]
        custom_properties="",   # type: Optional[str]
    ):
        # type: (...) -> str

        pos_ts = _a_get_pos_ts(pos_id)
        user_id = self.get_user_id(pos_id)

        try:
            return pos_ts.createOrder(
                user_id=user_id,
                table_id=table_id,
                price_list=price_list,
            )
        except Exception as ex:
            if "$INVALID_TABLE_STATUS" in ex.message:
                raise UiOutOfSync(
                    pos_id=pos_id,
                    custom_message="$INVALID_TABLE_STATUS_UI_MUST_BE_RELOADED",
                )

    def slice_service(
        self,
        pos_id,                 # type: str
        new_setup,              # type: str
        src_table_id,           # type: str
        seat="",                # type: Optional[str]
        abandon_table=False,    # type: Optional[bool]
    ):
        pos_ts = _a_get_pos_ts(pos_id)
        user_id = self.get_user_id(pos_id)

        return pos_ts.sliceService(
            userid=user_id,
            new_setup=new_setup,
            src_table_id=src_table_id,
            seat=seat,
            abandon_table=abandon_table,
        )

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

    @staticmethod
    def get_current_pos_id(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]
    ):
        # type: (...) -> str

        user_id = self.get_user_id(pos_id)
        pos_ts = _a_get_pos_ts(pos_id)

        return pos_ts.totalService(
            user_id=user_id,
            table_id=table_id,
            tip_rate=tip_rate
        )

    def reopen_table(self, pos_id, table_id):
        pos_ts = _a_get_pos_ts(pos_id)
        user_id = self.get_user_id(pos_id, False)

        return pos_ts.reopenService(user_id, table_id)

    def close_table(
        self,           # type: MsgBusTableService
        pos_id,         # type: str
        table_id,       # type: str
        user_id,        # type: str
    ):
        # type: (...) -> str

        pos_ts = _a_get_pos_ts(pos_id)
        user_id = user_id if user_id else self.get_user_id(pos_id)
        return pos_ts.closeService(user_id, table_id)

    def set_table_available(self, pos_id, table_id):
        pos_ts = _a_get_pos_ts(pos_id)
        user_id = self.get_user_id(pos_id, False)

        pos_ts.tableReady4Service(user_id, table_id)

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

    def clear_service_tenders(self, pos_id, table_id):
        pos_ts = _a_get_pos_ts(pos_id)
        user_id = self.get_user_id(pos_id, 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(pos_id)

        manager_id = sysactions.get_custom(
            model=model,
            key='Last Manager ID',
        )

        return pos_ts.changeServiceTip(
            user_id=user_id,
            table_id=table_id,
            tip_rate=percentage,
            tip_amount=amount,
            orders="",
            manager_id=manager_id,
        )

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

    def set_table_custom_properties(
        self,               # type: MsgBusTableService
        pos_id,             # type: int
        properties,         # type: str
        table_id=None,      # type: str
        service_id=None,    # type: str
    ):
        # type: (...) -> None

        pos_ts = _a_get_pos_ts(pos_id)
        pos_ts.setCustomProperties(
            properties=properties,
            table_id=table_id,
            service_id=service_id,
        )

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

    def register_service_tender(
        self,                   # type: MsgBusTableService
        pos_id,                 # type: str
        user_id,                # type: str
        table_id,               # type: str
        tender_id,              # type: int
        amount,                 # type: Any[float, str]
        tender_details="",      # type: str
    ):
        # type: (...) -> str

        pos_ts = _a_get_pos_ts(pos_id)
        user_id = user_id if user_id is not None else self.get_user_id(pos_id)

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

    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,           # type: MsgBusTableService
        pos_id,         # type: int
        seat_number,    # type: int
        order_id,       # type: int
        sale_line,      # type: eTree.Element
    ):
        # type: (...) -> None

        get_order_service(pos_id=pos_id).set_sale_line_custom_property(
            custom_property=CustomProperty(
                key="seat",
                value=str(seat_number),
            ),
            sale_line_identifier=SaleLineIdentifier(
                line_number=int(sale_line.get("lineNumber")),
                item_id=sale_line.get("itemId"),
                level=int(sale_line.get("level")),
                part_code=int(sale_line.get("partCode")),
            ),
        )

    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')

    def get_sectors(
        self,
        pos_id,     # type: Any[Union[str, int]]
    ):
        # type: (...) -> List[str]

        return get_table_service(pos_id=pos_id).get_sectors()

    def get_operator_sector_info(
        self,
        pos_id,     # type: Any[Union[str, int]]
    ):
        # type: (...) -> List[str]

        pos_ts = _a_get_pos_ts(pos_id)
        return pos_ts.getOperatorSectorInfo()

    def update_operator_sector_info(
        self,
        pos_id,         # type: Any[Union[str, int]]
        update_info,    # type: Dict[str, Dict]
    ):
        # type: (...) -> List[str]

        pos_ts = _a_get_pos_ts(pos_id)
        return pos_ts.updateOperatorSectorInfo(data=update_info)

    @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 get_user_id(pos_id, mandatory=True):
        model = get_current_model()

        try:
            user_id = model.find("Operator").get("id")
        except (Exception,) as ex:
            if mandatory is False:
                return '0'

            force_send_updated_model_to_ui(
                pos_id=pos_id,
                event_data="error get user id",
            )
            raise GettingUserInfoException(ex.args)

        return user_id

    @staticmethod
    def get_table_sector(
        table_picture,  # type: eTree.XML
        is_tab,         # type: bool
    ):
        # type: (...) -> str

        if is_tab:
            return "$TABS"
        else:
            table_sector = table_picture.get('sector')
            table_sub_sector = table_picture.get('subSector')
            if not table_sector and not table_sub_sector:
                return ""

            if not table_sub_sector:
                return table_sector

            return '{}/{}'.format(table_sector, table_sub_sector)

    @staticmethod
    def _get_table_sector_name(
        table,      # type: Table
    ):
        # type (...) -> str

        is_tab = "TAB" in table.id
        if is_tab:
            return "$TABS"

        if not table.sector and not table.sub_sector:
            return ""

        if not table.sub_sector:
            return table.sector

        return "{}/{}".format(table.sector, table.sub_sector)

    def get_service_seats(self, pos_id, table_id):
        table_info = self.get_table_info(pos_id, table_id)
        table_info_xml = eTree.XML(table_info)
        return int(table_info_xml.get("serviceSeats") or 0)

    @staticmethod
    def reset_store_layout(
        pos_id,     # type: str
    ):
        # type: (...) -> None

        pos_ts = _a_get_pos_ts(pos_id)
        pos_ts.reset_store_layout()


def _a_get_pos_ts(
    pos_id,  # type: str
):
    if pos_id != get_current_pos_id():
        model = get_model(pos_id)
        return get_pos_ts(model)

    model = get_current_model()
    return get_pos_ts(model)
