import json
from time import sleep

from application.model import OperatorCashInOut, TransferOrderInfo
from mbcontextmessagehandler import MbContextMessageBus
from messagebus import Message, DataType
from msgbus import TK_STORECFG_GET, TK_SYS_NAK, TK_POS_GETPOSLIST
from mwhelper import BaseRepository
from persistence import Connection
from typing import Optional, Dict, List

FISCAL_WRAPPER_GET_XML_TOKEN = 51380248
TK_SLCTRL_OMGR_ORDERPICT = 0x00A00002


class MessageBusRepository(BaseRepository):

    def __init__(self, message_bus):
        # type: (MbContextMessageBus) -> None
        super(MessageBusRepository, self).__init__(message_bus.mbcontext)

        self.message_bus = message_bus
        self.pos_list = None

    def read_sw_config(self, key):
        # type: (str) -> Optional[str]

        message = Message(TK_STORECFG_GET, data_type=DataType.param, data=key)
        msg = self.message_bus.send_message("StoreWideConfig", message)
        if msg.token == TK_SYS_NAK:
            return None
        return str(msg.data)

    def get_fiscal_xml_from_fiscal_wrapper(self, order_id):
        # type: (int) -> Optional[str]

        message = Message(FISCAL_WRAPPER_GET_XML_TOKEN, data_type=DataType.param, data=order_id)
        msg = self.message_bus.send_message("FiscalWrapper", message)
        if msg.token == TK_SYS_NAK:
            return None

        return str(msg.data) if msg.data else None

    def get_operator_time(self, session_id):
        # type: (str) -> Dict

        def inner_func(conn):
            # type: (Connection) -> Dict

            query = """SELECT strftime('%Y-%m-%dT%H:%M:%f', OpenTime) AS OpenTime,
                              strftime('%Y-%m-%dT%H:%M:%f', CloseTime) AS CloseTime
                       FROM UserSession
                       WHERE SessionId = '{}'""".format(session_id)
            cursor = conn.select(query)

            operator_time = {}
            for row in cursor:
                operator_time["OpenTime"] = row.get_entry("OpenTime")
                operator_time["CloseTime"] = row.get_entry("CloseTime")

            return operator_time

        return self.execute_with_connection(inner_func, service_name="Persistence")

    def get_transfers_by_session_id(self, pos_id, session_id):
        # type: (str, str) -> Dict

        def inner_func(conn):
            # type: (Connection) -> List

            query = """SELECT TenderId, Type, Amount FROM Transfer WHERE SessionId='{}'"""
            cursor = conn.select(query.format(session_id))

            database_transfers = {}
            for row in cursor:
                tender_id = row.get_entry("TenderId")
                if tender_id not in database_transfers:
                    database_transfers[tender_id] = {}

                transfer_type = row.get_entry("Type")
                transfer_amount = float(row.get_entry("Amount") or 0)
                if transfer_type not in database_transfers[tender_id]:
                    database_transfers[tender_id][transfer_type] = 0

                database_transfers[tender_id][transfer_type] += transfer_amount

            return database_transfers

        return self.execute_with_transaction(inner_func, str(pos_id))

    def get_transfer_order_info(self, session_id):
        transfer_order_info = {}

        def inner_func(conn):
            # type: (Connection) -> List

            query = """
                        SELECT 
                            ot.TenderId, 
                            COUNT(ot.TenderId) AS TenderQuantity, 
                            SUM(ot.TipAmount) AS TipAmount, 
                            SUM(ot.TenderAmount) - SUM(COALESCE(ot.ChangeAmount,0)) AS TotalTender
                        FROM Orders o
                        JOIN OrderTender ot ON ot.OrderId = o.OrderId
                        WHERE SessionId='{}'
                        GROUP BY ot.tenderId
                    """
            cursor = conn.select(query.format(session_id))

            for row in cursor:
                tender_id = row.get_entry("TenderId")
                if tender_id not in transfer_order_info:
                    transfer_order_info[tender_id] = TransferOrderInfo()

                transfer_order_info[tender_id].payment_quantity += int(row.get_entry("TenderQuantity") or 0)
                transfer_order_info[tender_id].tip_amount += float(row.get_entry("TipAmount") or 0)
                transfer_order_info[tender_id].total_tender += float(row.get_entry("TotalTender") or 0)

            return transfer_order_info

        self.execute_in_all_databases(inner_func, self._get_pos_list())
        return transfer_order_info

    def get_cash_in_out_by_session_id(self, session_id):
        # type: (str) -> List

        def inner_func(conn):
            # type: (Connection) -> List

            query = "SELECT * FROM Transfer WHERE SessionId = '{0}' AND Description IN ('{1}', '{2}')"\
                .format(session_id, OperatorCashInOut.CASH_IN, OperatorCashInOut.CASH_OUT)
            cursor = conn.select(query)

            cash_in_out_list = []
            for row in cursor:
                transfer = {"description": row.get_entry("Description"),
                            "manager_id": _get_manager_id(row),
                            "tender_type": row.get_entry("TenderId"),
                            "amount": float(row.get_entry("Amount") or 0),
                            "timestamp": row.get_entry("Timestamp")}
                cash_in_out_list.append(transfer)

            return cash_in_out_list

        def _get_manager_id(row):
            try:
                manager_id = row.get_entry("ManagerID")
            except ValueError:
                gl_account = row.get_entry("GLAccount")
                try:
                    parsed_gla_account = json.loads(gl_account)
                    manager_id = parsed_gla_account.get("manager", None)
                except (ValueError, TypeError):
                    manager_id = None

            return int(manager_id) if manager_id else None

        return self.execute_with_connection(inner_func)

    def get_product_categories(self, full=False):
        # type: (Optional[bool]) -> List

        def inner_func(conn):
            # type: (Connection) -> List

            if full:
                query = "SELECT * FROM ProductCustomParams"
            else:
                query = """SELECT ProductCode, CustomParamId, CustomParamValue
                           FROM (
                               SELECT
                                   ProductCode,
                                   substr(Tag,1,instr(Tag,'=')-1) AS CustomParamId,
                                   substr(Tag,instr(Tag,'=')+1) AS CustomParamValue
                               FROM ProductTags
                           ) Tags
                           WHERE Tags.CustomParamId IN ('Categories', 'SubCategories')"""
            cursor = conn.select(query)

            product_categories = []
            for row in cursor:
                product = {"ProductCode": row.get_entry("ProductCode"),
                           "CustomParamId": row.get_entry("CustomParamId"),
                           "CustomParamValue": row.get_entry("CustomParamValue")}
                product_categories.append(product)

            return product_categories

        return self.execute_with_connection(inner_func, service_name="Persistence")

    def get_declared_amount(self, business_period):
        # type: (str) -> Dict

        def inner_func(conn):
            # type: (Connection) -> Dict

            query = """SELECT TenderId, tdsum(Amount) 
                       FROM Transfer 
                       WHERE SessionId='{}' AND Type=6 
                       GROUP BY TenderId""".format(business_period)
            cursor = conn.select(query)

            declared_amounts = dict([(int(row.get_entry(0)), float(row.get_entry(1) or 0)) for row in cursor])
            return declared_amounts

        return self.execute_with_connection(inner_func, service_name="Persistence")

    def _get_last_order_id(self):
        # type: () -> int

        def inner_func(conn):
            # type: (Connection) -> int

            query = """SELECT MAX(SeqNo) FROM Sequencer WHERE SeqNm='OrderIdGen'"""
            cursor = conn.select(query)

            max_order_id = None
            for row in cursor:
                max_order_id = int(row.get_entry(0))
            return max_order_id

        return self.execute_with_connection(inner_func, service_name="Persistence")

    def get_last_order_picture(self):
        order_id = self._get_last_order_id()
        if order_id == 0:
            return None
        return self.get_order_picture(order_id)

    def get_order_picture(self, order_id):
        pos_id = ""
        params = (pos_id, str(order_id), "true")
        req_buf = "\0".join(str(p) for p in params)

        message = Message(TK_SLCTRL_OMGR_ORDERPICT, data_type=DataType.param, data=req_buf)
        msg = self.message_bus.send_message("ORDERMGR0", message)

        if msg.token == TK_SYS_NAK:
            return None
        params = msg.data.split('\0')
        return params[2]

    def _get_pos_list(self):
        # type: () -> List

        if self.pos_list is not None:
            return self.pos_list

        retry_cnt = 10
        while retry_cnt:
            msg = self.mb_context.MB_EasySendMessage("PosController", TK_POS_GETPOSLIST)
            if msg.token == TK_SYS_NAK:
                retry_cnt -= 1
                sleep(1)
            else:
                self.pos_list = sorted(map(int, msg.data.split("\0")))
                retry_cnt = 0

        return self.pos_list
