import base64
import cPickle as Pickle
from Queue import Queue, Empty
from datetime import datetime
from threading import Thread

import production.repository
from mw_helper import BaseRepository
from persistence import Connection
from production.model import ProductionOrder
from typing import (
    List,
    Dict,
)


class ProductionRepository(BaseRepository, production.repository.ProductionRepository):
    def __init__(self, mb_context, logger):
        super(ProductionRepository, self).__init__(mb_context)
        self.logger = logger

        self.order_queue = Queue()
        self.running = True

        self.processor_thread = Thread(target=self.consume_queue)
        self.processor_thread.daemon = True
        self.processor_thread.start()

    def consume_queue(self):
        while self.running:
            order_id = None
            try:
                order_to_save = self.order_queue.get(True, 5)
                order_id = order_to_save.order_id
                self.save_order_async(order_to_save)
            except Empty:
                continue
            except BaseException as _:  # noqa
                self.logger.exception("Error saving order: {}".format(order_id))

    def stop(self):
        self.running = False

    def save_order(self, order):
        self.order_queue.put(order)

    def save_order_async(self, order):
        def func(conn):
            # type: (Connection) -> None
            order_data = base64.b64encode(Pickle.dumps(order, Pickle.HIGHEST_PROTOCOL))
            conn.query(insert_query.format(order.order_id, order_data, order.created_at, 0))

        self.execute_with_connection(func)

    def get_all_orders(self):
        def func(conn):
            # type: (Connection) -> List[ProductionOrder]
            orders = []
            for row in conn.select(select_query):
                order_data = base64.b64decode(row.get_entry(0))
                order = Pickle.loads(order_data)
                orders.append(order)

            return orders

        return self.execute_with_connection(func)

    def get_orders_to_purge(self, limit_date):
        def func(conn):
            # type: (Connection) -> List[int]
            orders = []
            formatted_limit_date = limit_date.strftime("%Y-%m-%dT%H:%M:%S")
            for row in conn.select(get_orders_to_purge_query.format(formatted_limit_date)):
                order_id = int(row.get_entry("OrderId"))
                orders.append(order_id)

            return orders

        return self.execute_with_connection(func)

    def purge_orders(self, orders):
        def func(conn):
            # type: (Connection) -> None
            orders_list = [str(order) for order in orders]
            query = purge_orders_query.format(",".join(orders_list))
            conn.query(query)

        return self.execute_with_connection(func)

    def save_pick_list_order(
        self,
        order_id,           # type: int
        order_id_count,     # type: str
        jit,                # type: str
        course,             # type: str
        receipt,            # type: str
        status,             # type: bool
    ):
        # type: (...) -> None

        def func(
            conn,   # type: Connection
        ):
            # type: (...) -> None

            date = datetime.now().strftime(
                "%m/%d/%Y %H:%M:%S",
            )

            query = insert_pick_list_query.format(
                order_id,
                order_id_count,
                jit,
                course,
                receipt,
                date,
                status
            )

            conn.query(
                sql=query,
            )

        self.execute_with_connection(func)

    def get_all_pick_list_orders(
        self,
        orders_list,    # type: List[str]
    ):
        # type: (...) -> Dict

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

            orders = [str(order) for order in orders_list]

            query = select_pick_list_orders_query.format(",".join(orders))

            pick_list_orders = {}

            for row in conn.select(query):
                jit = row.get_entry(
                     col="JIT",
                )

                course_name = row.get_entry(
                   col="Course",
                )
                if course_name != '':
                    course_name = " - {}".format(
                        course_name,
                    )

                key = "{}{}".format(
                    jit,
                    course_name,
                )

                pick_list_orders[key] = row.get_entry(
                        col="Id",
                    )

            return pick_list_orders

        return self.execute_with_connection(func)

    def get_data_pick_list_order(
        self,
        pick_list_order_id,     # type: int
    ):
        # type: (...) -> str

        def func(conn):
            # type: (Connection) -> Optional[str]

            query = """SELECT Data FROM PicklistOrders WHERE Id == {}"""

            query = query.format(
                pick_list_order_id,
            )

            for row in conn.select(query):
                return row.get_entry(
                     col="Data",
                )

            return None

        return self.execute_with_connection(func)

insert_query = """
INSERT OR REPLACE INTO ProductionOrders
(OrderId, Object, CreatedAt, Purged)
VALUES
({}, '{}', '{}', {})
"""

select_query = """
SELECT Object FROM ProductionOrders WHERE PURGED = 0 ORDER BY CreatedAt DESC LIMIT 1000
"""

get_orders_to_purge_query = """
SELECT OrderId FROM ProductionOrders WHERE CreatedAt < '{}' ORDER BY CreatedAt ASC LIMIT 1000
"""

purge_orders_query = """
DELETE FROM ProductionOrders WHERE OrderId IN ({})
"""

insert_pick_list_query = """
INSERT OR REPLACE INTO PicklistOrders
(OrderId, OrderIdCount, JIT, Course, Data, CreatedAt, PrintOK)
VALUES
({}, '{}', '{}', '{}', '{}', '{}', {})
"""

select_pick_list_orders_query = """
SELECT Id, JIT, Course FROM PicklistOrders WHERE OrderId in ({}) ORDER BY CreatedAt
"""

