import base64
import cPickle as Pickle
import os
from Queue import Queue, Empty
from datetime import (
    datetime,
    timedelta,
)
from threading import (
    Thread,
    Lock,
)

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


lock_db = Lock()


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,       # type: ProductionRepository
        order,      # type: ProductionOrder
    ):
        # type: (...) -> None

        try:
            self.remove_forbidden_tags(
                items=order.items,
                order_id=str(order.order_id),
            )
        except Exception as _:
            self.logger.error("Error handling remove_forbidden_tags for order id {}".format(order.order_id))

        self.order_queue.put(order)

    def remove_forbidden_tags(
        self,           # type: ProductionRepository
        items,          # type: List[Item]
        order_id,       # type: str
    ):
        # type: (...) -> None

        forbidden_tags = [
            "wait-prep-time"
        ]

        for item in items:
            for forbidden_tag in forbidden_tags:
                if item.has_tag(forbidden_tag):
                    item.remove_tag(forbidden_tag)
                    self.logger.error("Denied TAG for Order ID {}: {}".format(order_id, forbidden_tag))

            if len(item.items) > 0:
                self.remove_forbidden_tags(
                    items=item.items,
                    order_id=order_id,
                )

    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.logger.info("Saving order: {} to Database".format(order.order_id))

        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,    # type: ProductionRepository
        orders,  # type: List
    ):
        # type: (...) -> None

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

                orders_list = [str(order) for order in orders]
                query = purge_orders_query.format(",".join(orders_list))
                conn.query(sql=query)

            return self.execute_with_connection(method=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

        with lock_db:
            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_picklist_id(
        self,
        order_id,           # type: int
        order_id_count,     # type: str
        jit,                # type: str
        course,             # type: str
    ):
        # type: (...) -> Optional[int]

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

            query = select_get_pick_list_id_query.format(order_id, order_id_count, jit, course)

            for row in conn.select(query):
                picklist_id = row.get_entry(col="Id")
                return int(picklist_id)

            return None

        return self.execute_with_connection(method=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)

    def delete_outdated_pick_list_orders(
        self,
        range_days,  # type: int
    ):
        # type: (...) -> None

        with lock_db:
            self.logger.info("Start delete_outdated_pick_list_orders")

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

                result_datetime = datetime.utcnow() - timedelta(days=range_days)
                formatted_date = result_datetime.strftime('%m/%d/%y %H:%M:%S')
                query = purge_picklist_orders_query.format(formatted_date)
                conn.query(sql=query)

            self.execute_with_transaction(method=inner_func)
            self.logger.info("End delete_outdated_pick_list_orders")

    def vacuum_database(
        self,
    ):
        # type: (...) -> Dict

        with lock_db:
            db_info = {}

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

                db_info["db_file"] = self._get_db_file(
                    conn=conn,
                    db_name_to_find="production",
                )
                db_info["old_file_size"] = self._get_file_size(file_name=db_info.get("db_file"))

                query = """
                    VACUUM production;
                """
                conn.query(sql=query)

            self.execute_with_connection(method=inner_func)
            db_info["new_file_size"] = self._get_file_size(file_name=db_info.get("db_file"))
            self.logger.info("Finished [vacuum_production_database]")

            return db_info

    @staticmethod
    def _get_db_file(
        conn,
        db_name_to_find,
    ):

        db_file = None
        pragma_cursor = conn.select("PRAGMA database_list;")
        for pragma_row in pragma_cursor:
            db_name, db_file = map(pragma_row.get_entry, (1, 2,))
            if db_name == db_name_to_find:
                break
        return db_file

    @staticmethod
    def _get_file_size(
        file_name,  # type: str
    ):
        # type: (...) -> int

        return os.path.getsize(file_name)


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_get_pick_list_id_query = """
SELECT Id FROM PicklistOrders WHERE OrderId = '{0}' AND OrderIdCount = '{1}' AND JIT = '{2}' AND Course = '{3}'
"""

purge_picklist_orders_query = """
DELETE FROM PicklistOrders WHERE CreatedAt <= '{}'
"""

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

