import json
import logging

from datetime import (
    datetime,
    timedelta,
)
from threading import Lock
from typing import Dict

import pyscripts
from bustoken import TK_SLCTRL_PMGR_GET_PRODUCTS
from msgbus import (
    FM_PARAM,
    TK_SYS_ACK,
    MBEasyContext,
)
from sysactions import (
    action,
    StopAction,
)

from model.customexception import (
    ProductNotExistsException,
    ErrorGettingProductData,
)

_product_price = {}
product_by_barcode = {}
products_cache = None
last_cache_time = None
lock = Lock()
last_snapshot_id = None
logger = logging.getLogger("PosActions")

mbcontext = pyscripts.mbcontext  # type: MBEasyContext


@action
def getProducts(pos_id, force=False, snapshot_id=None):
    global products_cache
    global last_cache_time
    global last_snapshot_id
    global lock
    global product_by_barcode
    force = str(force).lower() == "true"
    seconds_to_renew_cache = 3600
    if lock.locked():
        raise StopAction()
    with lock:
        now = datetime.now()
        seconds_to_renew = _get_seconds_to_renew(seconds_to_renew_cache) if not force else 0
        reached_cache_time = _reached_seconds_to_renew_cache(now, seconds_to_renew)
        changed_last_snapshot_id = snapshot_id is not None and last_snapshot_id != snapshot_id
        if not products_cache or reached_cache_time or changed_last_snapshot_id:
            last_cache_time = now
            products = json.loads(_get_products(pos_id=pos_id))
            products_cache = json.dumps(products[0])
            _load_product_prices(products[0])
            _load_product_by_barcode(products[1])

            if snapshot_id is not None:
                last_snapshot_id = snapshot_id

    return products_cache


def _load_product_prices(
    products_dict,       # type: Dict
):
    # type: (...) -> None

    for key, value in products_dict.items():
        prices = value["price"]
        if not prices:
            continue

        _product_price[key] = prices.values()[0]


def _load_product_by_barcode(
    barcode_dict,       # type: Dict
):
    # type: (...) -> None

    for key, value in barcode_dict.items():
        product_by_barcode[int(key)] = value


def _get_seconds_to_renew(seconds_to_renew_cache):
    seconds_to_renew = 180
    try:
        seconds_to_renew = int(seconds_to_renew_cache)
    except (TypeError, ValueError) as _:
        pass
    return seconds_to_renew


def _reached_seconds_to_renew_cache(now, seconds_to_renew_cache):
    return not last_cache_time or now > last_cache_time + timedelta(seconds=seconds_to_renew_cache)


def _get_products(pos_id):
    msg = mbcontext.MB_EasySendMessage(
        dest_name='ORDERMGR{}'.format(pos_id),
        token=TK_SLCTRL_PMGR_GET_PRODUCTS,
        format=FM_PARAM,
        data=pos_id,
    )
    if msg.token != TK_SYS_ACK:
        raise ErrorGettingProductData(msg.data)
    if msg.token == TK_SYS_ACK:
        return msg.data


def get_product_by_barcode(barcode):
    if barcode and int(barcode) in product_by_barcode:
        return product_by_barcode[int(barcode)]

    return None


def get_product_price_from_cache(plu, pos_id):
    global _product_price

    if _product_price == {}:
        getProducts(pos_id)

    if str(plu) not in _product_price:
        raise ProductNotExistsException(str(plu))

    return _product_price[str(plu)]
