# -*- coding: utf-8 -*-
import sqlite3
from logging import Logger

from cache_functions import UsersInfoCache
from dbrepository import SynchronizedDbApiTemplate
from messagebus import TokenCreator, TokenPriority, MessageBus
from messagehandler import MessageHandlerBuilder
from messagehandlerbuilder.token import ListenedEvents
from messageprocessor import (
    DefaultMessageProcessorExecutorFactory,
    MessageProcessorMessageHandler,
)
from messageprocessorutil import LoggingProcessorCallback
from production.box import ViewBox
from production.interactor import DeviceStatusChangedInteractor
from production.manager import ProductionManager, PicklistCleanerManager
from production.processor import (
    OrderModifiedProcessor,
    GetOrderMaxPrepTimeProcessor,
    GetOrderXmlProcessor,
    OrderModifiedIgnoredTypesProcessor,
    PurgeOrdersProcessor,
    GetPrintersInfoProcessor,
    PickListOrdersProcessor,
    DeviceStatusChangedProcessor,
    GetCoursesProcessor,
)
from production.processor.command import (
    SetOrderStateProcessor,
    ToggleTagLineProcessor,
    UndoProcessor,
    RefreshViewProcessor,
)
from production.treebuilder import TreeBuilder
from production.treebuilder.boxfactory import ProductionBoxFactory
from production.treebuilder.viewfactory import ViewFactory
from production.util import ProductionConfig
from sqliterepository import (
    ProductionRepository,
    ProductRepository,
)
from typing import (
    List,
    Dict,
    Union,
    Optional,
)

production_group = "C"
TK_PROD_REFRESH_VIEW = TokenCreator.create_token(TokenPriority.low, production_group, "1")
TK_PROD_SET_ORDER_STATE = TokenCreator.create_token(TokenPriority.low, production_group, "2")
TK_PROD_GET_ORDER_MAX_PREP_TIME = TokenCreator.create_token(TokenPriority.low, production_group, "3")
TK_PROD_GET_ORDER_XML = TokenCreator.create_token(TokenPriority.low, production_group, "4")  # 12582916
TK_PROD_UNDO = TokenCreator.create_token(TokenPriority.low, production_group, "7")
TK_PROD_TOGGLE_TAG_LINE = TokenCreator.create_token(TokenPriority.low, production_group, "8")

TK_PROD_PURGE = TokenCreator.create_token(TokenPriority.low, production_group, "103")

TK_PROD_GET_PRINTERS_INFO = TokenCreator.create_token(TokenPriority.low, production_group, "201")
TK_PROD_GET_COURSES = TokenCreator.create_token(TokenPriority.low, production_group, "202")

TK_PROD_ORDER_PICKLIST = TokenCreator.create_token(TokenPriority.low, production_group, "301")  # 12583681


class ProductionMessageHandlerBuilder(MessageHandlerBuilder):
    def __init__(
        self,                           # type: ProductionMessageHandlerBuilder
        message_bus,                    # type: MessageBus
        purge_interval,                 # type: int
        orders_life_time,               # type: int
        manager_log_level,              # type: str
        audit_logger_prod_states,       # type: List
        kds_manager,                    # type: str
        production_config,              # type: ProductionConfig
        loggers,                        # type: Dict[unicode, Logger]
        timeout_to_cleanup_devices,     # type: int
        product_connection_string,      # type: str
        production_connection_string,   # type: str
    ):
        # type: (...) -> None

        self.message_bus = message_bus
        self.purge_interval = purge_interval
        self.orders_life_time = orders_life_time
        self.manager_log_level = manager_log_level
        self.audit_logger_prod_states = audit_logger_prod_states
        self.production_config = production_config
        self.boxes_config = production_config.boxes_config
        self.branches_config = production_config.branches_config
        self.views_config = production_config.views_config
        self.clean_picklist_database_service_config = production_config.picklist_database_manager_config
        self.kds_manager = kds_manager
        self.loggers = loggers
        self.timeout_to_cleanup_devices = timeout_to_cleanup_devices
        self.product_connection_string = product_connection_string
        self.production_connection_string = production_connection_string
        self.production_repository = None   # type: Optional[ProductionRepository]
        self.product_repository = None      # type: Optional[ProductionRepository]
        self.production_connection = type   # type: Optional[sqlite3.connect]
        self.product_connection = type      # type: Optional[sqlite3.connect]

        self.production_manager = None  # type: Union[None, ProductionManager]
        self.message_handler = None
        self.user_cache_thread = None
        self.clean_picklist_database_service = None

    def build_singletons(self):
        if self.manager_log_level in self.loggers:
            manager_logger = self.loggers[self.manager_log_level]
        elif "ERROR" in self.loggers:
            manager_logger = self.loggers["ERROR"]
        elif len(self.loggers) > 0:
            manager_logger = self.loggers[self.loggers.keys()[0]]
        else:
            manager_logger = None

        self.product_repository = self._build_product_repository()
        self.production_repository = self._build_production_repository()

        self.production_manager = ProductionManager(
            self.production_repository,
            self.purge_interval,
            self.orders_life_time,
            self.message_bus,
            self.audit_logger_prod_states,
            self.kds_manager,
            manager_logger
        )

        production_box_factory = ProductionBoxFactory(
            self.production_manager,
            self.production_manager,
            self.production_manager,
            self.product_repository,
            self.loggers
        )

        view_factory = ViewFactory(
            message_bus=self.message_bus,
            loggers=self.loggers,
            production_manager=self.production_manager,
            kds_manager=self.kds_manager,
            timeout_to_cleanup_devices=self.timeout_to_cleanup_devices,
        )
        tree_builder = TreeBuilder(production_box_factory, view_factory)
        trees = tree_builder.build_trees(
            self.boxes_config,
            self.branches_config,
            self.views_config
        )

        root_boxes = []
        view_boxes = []
        for tree in trees:
            root_boxes.append(tree.root)
            leaves = [t for t in tree.leaves if isinstance(t, ViewBox)]
            view_boxes.extend(leaves)

        self.production_manager.set_root_boxes(root_boxes)
        self.production_manager.set_views(view_boxes)
        self.production_manager.initialize()

        self.user_cache_thread = UsersInfoCache(self.message_bus, manager_logger)
        self.user_cache_thread.daemon = True
        self.user_cache_thread.start()

        self.clean_picklist_database_service = PicklistCleanerManager(
            production_repository=self.production_repository,
            config=self.clean_picklist_database_service_config,
        )
        self.clean_picklist_database_service.start()

        order_modified_processor = OrderModifiedProcessor(
            self.production_manager,
            self.product_repository,
            self.user_cache_thread,
        )

        device_status_interactor = DeviceStatusChangedInteractor(production_manager=self.production_manager)
        device_status_changed_processor = DeviceStatusChangedProcessor(
            device_status_interactor=device_status_interactor,
        )

        event_processors = {
            ListenedEvents.DEVICE_STATUS_CHANGED: device_status_changed_processor,
        }

        order_modified_ignored_types_processor = OrderModifiedIgnoredTypesProcessor()
        event_processors.update({
            ListenedEvents.ORDER_MODIFIED: order_modified_processor,
            ListenedEvents.ORDER_MODIFIED_DO_TENDER: order_modified_ignored_types_processor,
            ListenedEvents.ORDER_MODIFIED_DISCOUNT_APPLIED: order_modified_ignored_types_processor,
            ListenedEvents.ORDER_MODIFIED_DISCOUNT_REMOVED: order_modified_ignored_types_processor,
            ListenedEvents.ORDER_MODIFIED_CLEAR_TENDERS: order_modified_ignored_types_processor,
            ListenedEvents.ORDER_MODIFIED_RESET_CURRENT_ORDER: order_modified_ignored_types_processor,
        })

        max_prep_time_processor = GetOrderMaxPrepTimeProcessor(self.production_manager, self.product_repository)
        message_processors = {
            TK_PROD_SET_ORDER_STATE: SetOrderStateProcessor(view_boxes),
            TK_PROD_TOGGLE_TAG_LINE: ToggleTagLineProcessor(view_boxes, self.loggers),
            TK_PROD_UNDO: UndoProcessor(view_boxes),
            TK_PROD_REFRESH_VIEW: RefreshViewProcessor(view_boxes),
            TK_PROD_GET_ORDER_MAX_PREP_TIME: max_prep_time_processor,
            TK_PROD_GET_ORDER_XML: GetOrderXmlProcessor(self.production_manager),
            TK_PROD_PURGE: PurgeOrdersProcessor(self.production_manager),
            TK_PROD_GET_PRINTERS_INFO: GetPrintersInfoProcessor(view_boxes),
            TK_PROD_GET_COURSES: GetCoursesProcessor(self.product_repository),
            TK_PROD_ORDER_PICKLIST: PickListOrdersProcessor(
                message_bus=self.message_bus,
                production_repository=self.production_repository,
            )
        }

        callbacks = [LoggingProcessorCallback(manager_logger)]

        self.message_handler = MessageProcessorMessageHandler(
            event_processors=event_processors,
            message_processors=message_processors,
            executor_factory=DefaultMessageProcessorExecutorFactory(callbacks),
        )

    def destroy_message_handler(
        self,
        message_handler,    # type: ProductionMessageHandlerBuilder
    ):
        # type: (...) -> None

        pass

    def build_message_handler(self, message, event):
        return self.message_handler

    def destroy_singletons(self):
        self.production_manager.stop()
        self.user_cache_thread.stop()
        self.production_repository.stop()
        self.clean_picklist_database_service.stop()
        self.product_connection.close()
        self.production_connection.close()

    def _build_product_repository(
        self,  # type: ProductionMessageHandlerBuilder
    ):
        # type: (...) -> ProductRepository

        self.product_connection = self._get_connection(
            connection_string=self.product_connection_string
        )

        return ProductRepository(
            connection=self.product_connection,
        )

    def _build_production_repository(
        self,   # type: ProductionMessageHandlerBuilder
    ):
        # type: (...) -> ProductionRepository

        self.production_connection = self._get_connection(
            connection_string=self.production_connection_string
        )
        synchronized_production_connection = SynchronizedDbApiTemplate(
            conn=self.production_connection,
        )
        return ProductionRepository(
            loggers=self.loggers,
            connection=synchronized_production_connection,
        )

    @staticmethod
    def _get_connection(
        connection_string,  # type:str
    ):
        # type: (...) -> sqlite3.Connection

        return sqlite3.connect(
            database=connection_string,
            check_same_thread=False,
        )
