import json
import inspect
from typing import (
    Optional,
    Union,
    List,
    Any,
)

from pyscripts import mbcontext
from posot import OrderTaker, OrderTakerException
from sysactions import send_message, get_podtype, get_operator_session, get_business_period
from systools import sys_log_info
from msgbus import TK_SYS_NAK, TK_CMP_MESSAGE, FM_STRING, FM_PARAM, TK_SYS_ACK, MBException

__all__ = ("TableService", "TableServiceException", "get_pos_ts")
DEFAULT_TIMEOUT = 300 * 1000 * 1000


class TableServiceException(OrderTakerException):
    def __init__(self, errmsg=""):
        super(TableServiceException, self).__init__("-1", errmsg)


class TableService(OrderTaker):
    def __init__(self, pos_id, mb_easy_context=None, order_mgr=None, product_mgr=None, table_mgr=None):
        super(TableService, self).__init__(pos_id, mb_easy_context, order_mgr, product_mgr)
        self.table_mgr = table_mgr or "TABLEMGR"

    def send_table_message(self, params):
        sys_log_info("TableService.{0}({1})".format(inspect.getframeinfo(inspect.currentframe()).function, params))
        req_buf = "\0".join([str(p) for p in params])
        msg = send_message(self.table_mgr, TK_CMP_MESSAGE, format=FM_PARAM, data=req_buf, timeout=DEFAULT_TIMEOUT)
        if msg.token == TK_SYS_NAK:
            message = "API exception: {0}({1})".format(inspect.getframeinfo(inspect.currentframe()).function, params)
            raise TableServiceException(errmsg=(msg.data if msg.format == FM_STRING and msg.data else message))

        return msg

    def listTables(self, user_id=None, table_id=None, with_props=None):
        params = ("LIST_TABLES", self._posid, user_id or "", table_id or "", with_props or "")
        msg = self.send_table_message(params)
        return json.loads(msg.data)

    def storePicture(self, force_update=False):
        params = ("STORE_PICTURE", self._posid, (1 if force_update else 0))
        msg = self.send_table_message(params)
        return msg.data

    def setCustomProperty(self, prop_key, prop_value="", table_id=None, service_id=None):
        params = ("SET_CUSTOM_PROPERTY", self._posid, prop_key, prop_value, table_id or "", service_id or "")
        self.send_table_message(params)

    def setCustomProperties(
        self,
        properties='',      # type: str
        table_id=None,      # type: str
        service_id=None,    # type: str
    ):
        # type: (...) -> None

        params = ("SET_CUSTOM_PROPERTIES", self._posid, properties, table_id or "", service_id or "")
        self.send_table_message(params)

    def getCustomProperty(self, prop_key, table_id=None, service_id=None):
        params = ("GET_CUSTOM_PROPERTY", self._posid, prop_key, table_id or "", service_id or "")
        msg = self.send_table_message(params)
        return json.loads(msg.data)

    def getTablePicture(self, table_id):
        params = ("TABLE_PICTURE", self._posid, table_id)
        msg = self.send_table_message(params)
        return msg.data

    def getServicePicture(self, service_id):
        params = ("SERVICE_PICTURE", self._posid, service_id)
        msg = self.send_table_message(params)
        return msg.data

    def getTableInfo(self, table_id):
        params = ("TABLE_INFO", self._posid, table_id)
        msg = self.send_table_message(params)
        return msg.data

    def recallService(self, user_id, table_id):
        params = ("RECALL_SERVICE", self._posid, user_id, table_id)
        msg = self.send_table_message(params)
        return msg.data

    def storeService(self, user_id, table_id, order_id=None):
        params = ("STORE_SERVICE", self._posid, user_id, table_id, order_id or "")
        self.send_table_message(params)
        return True

    def startService(self, user_id, b_period, table_id="", num_seats="", custom_props=None, source_table_id=""):
        props = [] if custom_props is None else json.dumps(custom_props)
        params = ("START_SERVICE", self._posid, user_id, table_id, b_period, num_seats, props, source_table_id)
        msg = self.send_table_message(params)
        return msg.data

    def sliceService(
        self,
        userid,                 # type: str
        new_setup,              # type: str
        src_table_id,           # type: str
        dst_table_id="",        # type: Optional[str]
        seat="",                # type: Optional[str]
        abandon_table=False,    # type: Optional[bool]
    ):
        params = (
            "SLICE_SERVICE",
            self._posid,
            userid,
            new_setup,
            src_table_id,
            dst_table_id,
            seat,
            abandon_table
        )
        msg = self.send_table_message(params=params)
        return json.loads(msg.data)

    def abandonService(self, user_id, table_id):
        params = ("ABANDON_SERVICE", self._posid, user_id, table_id)
        self.send_table_message(params)

    def createOrder(self, user_id, table_id, price_list=""):
        params = ("CREATE_ORDER", self._posid, user_id, table_id, price_list)
        msg = self.send_table_message(params)
        return msg.data

    def setParentTable(
        self,
        user_id,            # type: str
        table_id,           # type: str
        parent_table_id     # type: str
    ):
        # type: (...) -> None

        params = (
            "SET_PARENT_TABLE",
            self._posid,
            user_id,
            table_id,
            parent_table_id
        )
        self.send_table_message(params)

    def setCurrentOrder(self, user_id, table_id, order_id):
        params = ("SET_CURRENT_ORDER", self._posid, user_id, table_id, order_id)
        self.send_table_message(params)

    def voidServiceOrder(self, user_id, table_id, order_id):
        params = ("VOID_SERVICE_ORDER", self._posid, user_id, table_id, order_id)
        self.send_table_message(params)

    def registerServiceTender(self, user_id, table_id, tender_id, amount="", tender_detail=""):
        params = ("REGISTER_SERVICE_TENDER", self._posid, user_id, table_id, tender_id, amount, tender_detail)
        msg = self.send_table_message(params)
        return msg.data

    def updateTableTenders(self, user_id, table_id):
        params = ("UPDATE_TABLE_TENDERS", self._posid, user_id, table_id)
        msg = self.send_table_message(params)
        return msg.data

    def clearServiceTenders(self, user_id, table_id, service_tender_id="", delete_order_tender=True):
        params = ("CLEAR_SERVICE_TENDERS", self._posid, user_id, table_id, service_tender_id, delete_order_tender)
        msg = self.send_table_message(params)
        return msg.data

    def totalService(self, user_id, table_id, tip_rate="", tip_amount="", sale_type="EAT_IN"):
        params = ("TOTAL_SERVICE", self._posid, user_id, table_id, tip_rate, tip_amount, sale_type)
        msg = self.send_table_message(params)
        return msg.data

    def changeServiceTip(
        self,
        user_id,            # type: str
        table_id,           # type: str
        tip_rate="",        # type: str
        tip_amount="",      # type: str
        orders="",          # type: str
        manager_id="",      # type: str
    ):
        # type: (...) -> Any

        params = (
            "CHANGE_SERVICE_TIP",
            self._posid,
            user_id,
            table_id,
            tip_rate or "",
            tip_amount or "",
            orders or "",
            manager_id or "",
        )

        msg = self.send_table_message(
            params=params,
        )

        return msg.data

    def closeService(self, user_id, table_id):
        params = ("CLOSE_SERVICE", self._posid, user_id, table_id)
        msg = self.send_table_message(params)
        return msg.data

    def reopenService(self, user_id, table_id):
        params = ("REOPEN_SERVICE", self._posid, user_id, table_id)
        msg = self.send_table_message(params)
        return msg.data

    def joinTables(self, user_id, from_table_id, dest_table_id, number_of_seats=""):
        params = ("JOIN_TABLES", self._posid, user_id, from_table_id, dest_table_id, number_of_seats)
        self.send_table_message(params)

    def moveTable(
        self,
        user_id,                        # type: int
        from_table_id,                  # type: str
        dest_table_id,                  # type: str
        number_of_seats="",             # type: Optional[str]
        tables_to_set_available=None,   # type: Optional[List]
        mode="seats",                   # type: Optional[str]
        ignore_totaled_orders=False     # type: Optional[bool]
    ):
        params = (
            "MOVE_TABLE",
            self._posid,
            user_id,
            from_table_id,
            dest_table_id,
            number_of_seats,
            tables_to_set_available,
            mode,
            ignore_totaled_orders
        )
        self.send_table_message(params)

    def moveTableOrder(
            self,
            user_id,            # type: int
            from_table_id,      # type: str
            dest_table_id,      # type: str
            order_id,           # type: int
            number_of_seats=""  # type: Optional[Union[str, int]]
    ):
        # type: (...) -> None

        params = ("MOVE_TABLE_ORDER", self._posid, user_id, from_table_id, dest_table_id, order_id, number_of_seats)
        self.send_table_message(params)

    def moveChildTable(
            self,
            user_id,            # type: int
            from_table_id,      # type: str
            dest_table_id,      # type: str
            child_table_id,     # type: int
            custom_props=None   # type: str
    ):
        # type: (...) -> None

        props = [] if custom_props is None else json.dumps(custom_props)
        params = ("MOVE_CHILD_TABLE", self._posid, user_id, from_table_id, dest_table_id, child_table_id, props)
        self.send_table_message(params)

    def unlinkTables(self, user_id, table_id, unlink_table_id=""):
        params = ("UNLINK_TABLES", self._posid, user_id, table_id, unlink_table_id)
        self.send_table_message(params)

    def tableReady4Service(self, user_id, table_id):
        params = ("TABLE_READY4SERVICE", self._posid, user_id, table_id)
        self.send_table_message(params)

    def changeTableUser(self, user_id, table_id="", service_id=""):
        params = ("CHANGE_TABLE_USER", self._posid, user_id, table_id, service_id)
        self.send_table_message(params)

    def changeNumberOfSeats(self, user_id, number_of_seats, table_id="", service_id=""):
        params = ("CHANGE_NUMBER_OF_SEATS", self._posid, user_id, number_of_seats, table_id, service_id)
        self.send_table_message(params)

    def moveOrderToService(self, user_id, order_id, table_id="", service_id=""):
        params = ("MOVE_ORDER_TO_SERVICE", self._posid, user_id, table_id, order_id, service_id)
        self.send_table_message(params)

    def getSectors(
        self,
    ):
        # type: (...) -> List[str]

        params = ("GET_SECTORS", self._posid)
        msg = self.send_table_message(params=params)
        return msg.data

    def getOperatorSectorInfo(
        self,
        force=False,    # type: Optional[bool]
    ):
        # type: (...) -> List[str]

        params = ("GET_OPERATOR_SECTOR_INFO", self._posid, force)
        msg = self.send_table_message(params=params)
        return msg.data

    def updateOperatorSectorInfo(
        self,
        data,   # type: str
    ):
        # type: (...) -> List[str]

        params = ("UPDATE_OPERATOR_SECTOR_INFO", self._posid, data)
        msg = self.send_table_message(params=params)
        return msg.data

    def reset_store_layout(
        self,
    ):
        # type: (...) -> None

        params = ("RESET_STORE_LAYOUT", self._posid)
        self.send_table_message(params=params)

def get_pos_ts(model, order_mgr=None, product_mgr=None, table_mgr=None):
    pos_id = model.get("posId", "1") if model is not None else "1"
    pos_ot = TableService(pos_id, mbcontext, order_mgr, product_mgr, table_mgr)
    if model is not None:
        pos_ot.podType = get_podtype(model)
        pos_ot.businessPeriod = get_business_period(model)
        pos_ot.sessionId = get_operator_session(model)
        operator = model.find("Operator")
        if operator is not None:
            pos_ot.operatorId = operator.get("id")

    return pos_ot
