import collections
import json
import sys

from json import loads
from threading import Lock
from typing import Optional, List, Dict

from persistence import Connection
from mwhelper import BaseRepository
from mbcontextmessagehandler import MbContextMessageBus

from application.model import Configurations
from application.util import get_range_discount_ids


class NDiscountRepository(BaseRepository):

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

        self.logger = configs.logger
        self.configs = configs
        self.message_bus = message_bus
        self.product_cache = []
        self.product_group_cache = {}
        self.service_periods_price_keys_cache = {}
        self.service_periods_cache = {}
        self.automatic_benefits = []
        self.popup_lock = Lock()

    def build_singletons(self):
        self._create_discount_id_range()
        self._get_automatic_benefits()
        self._get_all_products()
        self._get_all_product_groups()
        self._get_service_periods_price_keys()
        self._get_service_periods()

    def get_benefits_by_visibility_from_database(self, only_visible):
        # type: (bool) -> List[dict]

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

            query = "SELECT BenefitId, BenefitJSON FROM StoredBenefits"
            cursor = conn.select(query)

            all_benefits = []
            for row in cursor:
                benefit_item = loads(row.get_entry(1))
                if not only_visible or benefit_item.get("visible", False):
                    all_benefits.append(dict(benefitId=row.get_entry(0), benefitJson=benefit_item))

            return all_benefits

        return self.execute_with_connection(inner_func, service_name=self.configs.persistence_name)

    def _get_all_products(self):
        # type: () -> None

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

            query = "SELECT ProductCode FROM Product"
            cursor = conn.select(query)

            all_products = []
            for row in cursor:
                product_item = int(row.get_entry(0))
                all_products.append(product_item)

            return all_products

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

    def _get_service_periods_price_keys(self):
        # type: () -> None

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

            query = "SELECT PriceKey, PriceListId FROM Price"
            cursor = conn.select(query)

            all_service_periods_price_keys = {}
            for row in cursor:
                price_key, price_list = int(row.get_entry(0)), str(row.get_entry(1))
                all_service_periods_price_keys[price_key] = price_list

            return all_service_periods_price_keys

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

    def _get_service_periods(self):
        # type: () -> None

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

            query = "SELECT ServicePeriodId, PriceListId FROM ServicePeriod"
            cursor = conn.select(query)

            all_service_periods = {}
            for row in cursor:
                service_period_id, price_list = int(row.get_entry(0)), str(row.get_entry(1))
                all_service_periods[service_period_id] = price_list

            return all_service_periods

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

    def _get_all_product_groups(self):
        # type: () -> None

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

            query = """SELECT 
                           ProductCode, CustomParamValue 
                       FROM ProductCustomParams 
                       WHERE CustomParamId = 'PLUFamilyGroup'"""
            cursor = conn.select(query)

            all_product_groups = {}
            for row in cursor:
                product = int(row.get_entry(0))
                groups = str(row.get_entry(1)).split(';')
                for g in groups:
                    if g in all_product_groups:
                        all_product_groups[g].append(product)
                    else:
                        all_product_groups[g] = [product]

            return all_product_groups

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

    def _create_discount_id_range(self):
        # type: () -> None

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

            query = "SELECT DiscountId FROM Discounts"
            cursor = conn.select(query)

            discounts_id = [int(row.get_entry(0)) for row in cursor]
            range_discount_ids = get_range_discount_ids()
            for range_id in range_discount_ids:
                if range_id not in discounts_id:
                    query = """INSERT OR REPLACE INTO Discounts (DiscountId, DiscountDescr, Script, ValidFrom) {0}"""
                    query = query.format("VALUES ({0}, '', '', '2010-04-29')")
                    query = query.format(range_id)
                    conn.select(query)
                    discounts_id.append(range_id)

            return discounts_id

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

    def _get_automatic_benefits(self):
        # type: () -> None

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

            query = "SELECT BenefitId, BenefitJSON FROM StoredBenefits"
            cursor = conn.select(query)

            discounts = []
            benefits = {}
            for row in cursor:
                if row.get_entry(1) is None or "automatic" not in row.get_entry(1):
                    continue

                data = json.loads(row.get_entry(1))
                if data["automatic"] in ["ON_TOTAL", "ON_SALE", "ON_TABLE_OPEN", "ON_TAB_OPEN"]:
                    benefit = json.dumps(dict(benefitId=row.get_entry(0), benefit=data))
                    benefit_priority = data.get("priority", sys.maxsize)
                    if benefit_priority not in benefits:
                        benefits[benefit_priority] = []
                    benefits[benefit_priority].append(benefit)

            ordered_benefits = collections.OrderedDict(sorted(benefits.items()))
            for x in ordered_benefits.items():
                discounts.extend(x[1])
            return discounts

        self.automatic_benefits = self.execute_with_connection(inner_func, service_name=self.configs.persistence_name)

    def get_all_products(self):
        # type: () -> List[int]

        return self.product_cache

    def get_all_automatic_benefits(self):
        # type: () -> List[str]

        return self.automatic_benefits

    def get_all_service_periods_price_keys(self):
        # type: () -> dict

        return self.service_periods_price_keys_cache

    def get_all_service_periods(self):
        # type: () -> dict

        return self.service_periods_cache

    def get_all_product_groups(self):
        # type: () -> Dict

        return self.product_group_cache
