import json
import logging
from datetime import (
    datetime,
    timedelta,
)
from threading import Lock

from typing import Optional
from xml.etree import cElementTree as eTree

from bustoken import (
    TK_LOYALTY_GET_ID_TYPE,
    TK_LOYALTY_GET_LOYALTY_ID,
    TK_LOYALTY_IS_AVAILABLE,
    TK_LOYALTY_ID_NOT_FOUND,
)
from msgbus import (
    FM_PARAM,
    MBException,
    TK_SYS_ACK,
)
from sysactions import (
    send_message,
    show_messagebox,
)

from mw_helper import show_qr_code

from utilfunctions import (
    get_custom_property_value,
    parse_qr_tag,
)

show_welcome_message = {}
show_final_message = {}

loyalty_available_cache = None
loyalty_available_cache_time = datetime.min  # type: datetime
loyalty_available_lock = Lock()
five_seconds_timeout = 5000000


class LoyaltyInteractor(object):
    def __init__(
        self,
        pos_id=None,    # type: Optional[str]
    ):
        # type: (...) -> None

        self.pos_id = pos_id
        self.logger = logging.getLogger("PosActions")
        self.loyalty_id_type = None
        self.welcome_message_types = {
            "DOCUMENT": "$INSERT_CUSTOMER_DOC",
            "PHONE": "$INSERT_CUSTOMER_PHONE",
            "OTHER": "$INSERT_CUSTOMER_NUMBER",
        }

    def is_loyalty_available(
        self
    ):
        # type: (...) -> Optional[bool]

        global loyalty_available_cache_time
        global loyalty_available_cache

        with loyalty_available_lock:
            now = datetime.now()
            seconds_to_renew = 300
            renew_cache = now > loyalty_available_cache_time + timedelta(seconds=seconds_to_renew)
            if loyalty_available_cache is None or renew_cache:
                loyalty_available_cache = self._get_loyalty_status()
                loyalty_available_cache_time = now
                if loyalty_available_cache is None:
                    return None

            return loyalty_available_cache

    @staticmethod
    def _get_loyalty_status():
        # type: (...) -> Optional[bool]

        try:
            fifteen_seconds_timeout = 15000000
            response = send_message(
                dest_name="Loyalty",
                token=TK_LOYALTY_IS_AVAILABLE,
                format=FM_PARAM,
                timeout=fifteen_seconds_timeout,
            )
            return response.token == TK_SYS_ACK
        except (Exception,):
            return None

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

        if self.loyalty_id_type:
            return self.loyalty_id_type

        response = send_message(
            dest_name="Loyalty",
            token=TK_LOYALTY_GET_ID_TYPE,
            format=FM_PARAM,
            data="LoyaltyIdentifierType",
            timeout=five_seconds_timeout,
        )
        if response.token == TK_SYS_ACK:
            self.loyalty_id_type = response.data

        return self.loyalty_id_type

    @staticmethod
    def set_loyalty_info(
        pos_id,         # type: int
        loyalty_id,     # type: str
        order_id,       # type: int
        table_id,       # type: str
        seat,           # type: str
    ):
        # type: (...) -> Optional[str]

        if not loyalty_id:
            return

        try:
            request = {
                "posId": pos_id,
                "loyaltyId": loyalty_id,
                "orderId": order_id,
                "tableId": table_id,
                "seat": seat,
            }
            ret = send_message(
                dest_name="Loyalty",
                token=TK_LOYALTY_GET_LOYALTY_ID,
                format=FM_PARAM,
                data=json.dumps(request).encode("utf-8"),
                timeout=five_seconds_timeout,
            )
            if ret.token == TK_SYS_ACK:
                return ret.data
            if ret.token == TK_LOYALTY_ID_NOT_FOUND:
                return None

            raise LoyaltyInteractor.LoyaltyPartnerError(ret.data)
        except MBException as ex:
            if "SE_NOTFOUND" in ex.message:
                return None

            raise ex

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

        welcome_message = self.welcome_message_types.get(self.get_loyalty_id_type())
        return welcome_message, '$INFORM_LOYALTY_ID'

    def set_show_customer_welcome_message(
        self,
        show,    # type: bool
    ):
        # type: (...) -> None

        global show_welcome_message

        if self.pos_id not in show_welcome_message:
            show_welcome_message[self.pos_id] = show

    def get_show_customer_welcome_message(
        self,
    ):
        # type: (...) -> bool

        global show_welcome_message

        if self.pos_id in show_welcome_message:
            return show_welcome_message[self.pos_id]

        return False

    def set_show_customer_final_message(
        self,
        show,    # type: bool
    ):
        # type: (...) -> None

        global show_final_message

        if self.pos_id not in show_final_message:
            show_final_message[self.pos_id] = show

    def show_loyalty_customer_final_message(
        self,
        order_picture=None,      # type: Optional[eTree.Element]
    ):
        # type: (...) -> None

        self.logger.debug("showing customer final message")

        try:
            loyalty_customer_info = get_custom_property_value(
                pos_id=self.pos_id,
                custom_property="LOYALTY_CUSTOMER_INFO",
                order_picture=order_picture,
            )
            loyalty_customer_data = json.loads(loyalty_customer_info or "{}")
        except Exception:  # noqa
            self.exception("error getting/parsing loyalty customer info")
            loyalty_customer_data = {}

        customer_info = loyalty_customer_data.get("customerInfo", {})
        additional_info = customer_info.get("additionalInfo", {})
        end_sale_message = additional_info.get("endSaleMessage", None)

        if end_sale_message and self._show_final_message():
            title = "$LOYALTY_FINAL_MESSAGE_CUSTOMER_HEADER"
            message, qr_data = parse_qr_tag(text=end_sale_message)
            if qr_data:
                show_qr_code(
                    pos_id=self.pos_id,
                    code_data=qr_data,
                    buttons="$OK",
                    title=title,
                    info_text=message,
                )
            else:
                show_messagebox(
                    posid=self.pos_id,
                    message=message,
                    title=title,
                )

    def _show_final_message(
        self,
    ):
        # type: (...) -> bool

        global show_final_message

        if self.pos_id in show_final_message:
            return show_final_message[self.pos_id]

        return False

    class LoyaltyPartnerError(Exception):
        pass
