from datetime import datetime
import os
from logging import Logger

import cfgtools
import logging

import persistence
import sysactions

from helper import import_pydevd, config_logger
from msgbus import MBEasyContext
from systools import sys_parsefilepath

from messagehandlerbuilder import ProductionMessageHandlerBuilder
from messagehandlerbuilder.token import all_events
from mbcontextmessagehandler import MbContextMessageBus, MbContextMessageHandler
from production.model import NoBoxesConfiguredException, NoViewsConfiguredException, PicklistCleanerManagerConfig
from production.util import ProductionConfig
from production.treebuilder import ViewConfiguration, BoxConfiguration, TreeConfig
from typing import List, Dict


def main():
    import_pydevd(os.environ["LOADERCFG"], 9131, False)
    config_logger(os.environ["LOADERCFG"], "ProductionPerformanceEvent", max_files=5, max_size=12582912)
    config_logger(os.environ["LOADERCFG"], "ProductionPerformanceCommand", max_files=5, max_size=12582912)
    config_logger(os.environ["LOADERCFG"], "ProductionPrinter", max_files=5, max_size=12582912)
    config_logger(os.environ["LOADERCFG"], "PicklistDatabaseManager", max_files=5, max_size=12582912)

    config = cfgtools.read(os.environ["LOADERCFG"])
    max_files = int(config.find_value("Production.Manager.MaxFileLogs", 5))
    config_logger(os.environ["LOADERCFG"], "Production", max_files=max_files, max_size=12582912, add_error_handler=True)

    base_logger = logging.getLogger("Production")
    debug_logger = build_logger(base_logger, "ProductionDebug", logging.DEBUG)
    info_logger = build_logger(base_logger, "ProductionInfo", logging.INFO)
    warning_logger = build_logger(base_logger, "ProductionWarning", logging.WARNING)
    error_logger = build_logger(base_logger, "ProductionError", logging.ERROR)

    loggers = {
        u"DEBUG": debug_logger,
        u"INFO": info_logger,
        u"WARNING": warning_logger,
        u"ERROR": error_logger,
    }

    boxes_config = parse_box_configurations(config, "Production.Boxes")
    views_config = parse_view_configurations(config)
    branches_config = parse_box_configurations(config, "Production.Branches")
    clean_picklist_database_service_config = get_clean_picklist_database_service_config(config=config)

    if len(boxes_config) == 0:
        raise NoBoxesConfiguredException("No boxes configured")
    if len(views_config) == 0:
        raise NoViewsConfiguredException("No views configured")

    purge_interval = int(config.find_value("Production.PurgeInterval", "1"))
    orders_life_time = int(config.find_value("Production.OrdersLifeTime", "20"))
    manager_log_level = config.find_value("Production.Manager.LogLevel", "ERROR")
    audit_logger_prod_states = config.find_values("Production.Manager.AuditLogProdStates") or []
    kds_manager = config.find_value("Production.KdsManager", "NKDSCTRL")
    timeout_to_cleanup_devices = int(config.find_value("Production.TimeToCleanDevicesCache") or 5)

    mb_context = MBEasyContext("NewProduction")

    sysactions.mbcontext = mb_context

    message_bus = MbContextMessageBus(mb_context)
    production_config = ProductionConfig(
        boxes_config=boxes_config,
        branches_config=branches_config,
        views_config=views_config,
        picklist_database_manager_config=clean_picklist_database_service_config,
    )

    production_connection_string = _get_connection_string(
        config=config,
        db_name="production"
    )

    product_connection_string = _get_connection_string(
        config=config,
        db_name="product"
    )
    message_handler_builder = ProductionMessageHandlerBuilder(
        message_bus=message_bus,
        purge_interval=purge_interval,
        orders_life_time=orders_life_time,
        manager_log_level=manager_log_level,
        audit_logger_prod_states=audit_logger_prod_states,
        kds_manager=kds_manager,
        production_config=production_config,
        loggers=loggers,
        timeout_to_cleanup_devices=timeout_to_cleanup_devices,
        production_connection_string=production_connection_string,
        product_connection_string=product_connection_string,
    )

    message_handler = MbContextMessageHandler(
        message_bus=message_bus,
        service_name="ProductionSystem",
        service_type="Production",
        required_services="StoreWideConfig|NKDSCTRL|Persistence",
        event_handler_builder=message_handler_builder,
    )

    message_handler.subscribe_reentrant_events(
        subjects=all_events()
    )

    _wait_persistence(
        message_bus_context=mb_context,
    )

    return message_handler.handle_events()


def parse_view_configurations(config):
    # type: (cfgtools.Group) -> List[ViewConfiguration]
    ret = []
    views_group = config.find_group("Production.Views")  # type: cfgtools.Group
    for group in views_group.groups:
        name = group.name
        if name is None:
            raise Exception("View without name")

        view_type = None
        log_level = "ERROR"
        extra_config = {}
        for key in group.keys:
            if key.name == "Type":
                view_type = key.value()
            elif key.name == "LogLevel":
                log_level = key.value()
            else:
                extra_config[key.name] = key.values if len(key.values) > 1 else key.value()

        if view_type is None:
            raise Exception("View without type: {}".format(name))

        ret.append(ViewConfiguration(name, view_type, log_level, extra_config))

    return ret


def parse_box_configurations(config, config_path):
    # type: (cfgtools.Group, str) -> List[TreeConfig]
    ret = []
    boxes_group = config.find_group(config_path)  # type: cfgtools.Group
    if not boxes_group:
        return ret

    for tree in boxes_group.groups:  # type: cfgtools.Group
        tree_boxes = []
        branch_name = tree.name
        for group in tree.groups:
            name = group.name.strip()
            if name is None:
                raise Exception("View without name")

            box_type = None
            log_level = "ERROR"
            extra_config = {}
            sons = []
            for key in group.keys:
                if key.name == "Type":
                    box_type = key.value().strip()
                elif key.name == "LogLevel":
                    log_level = key.value().strip()
                elif key.name == "Sons":
                    if len(key.values) > 0:
                        sons.extend([x.strip() for x in key.values])
                    else:
                        sons.append(key.value().strip())
                else:
                    extra_config[key.name] = key.values if len(key.values) > 1 else key.value()

            for inner_group in group.groups:
                extra_config[inner_group.name] = get_extra_config(inner_group)

            if box_type is None:
                raise Exception("View without type: {}".format(name))

            tree_boxes.append(BoxConfiguration(name, box_type, sons, log_level, extra_config))
        ret.append(TreeConfig(branch_name, tree_boxes))

    return ret


def get_clean_picklist_database_service_config(
    config,     # type: cfgtools.Group
):
    # type: (...) -> PicklistCleanerManagerConfig

    start_time = config.find_value("Production.Maintenance.Window.StartTime") or None
    end_time = config.find_value("Production.Maintenance.Window.EndTime") or None
    if start_time and end_time:
        start_time = datetime.strptime(start_time, "%H:%M:%S").time()
        end_time = datetime.strptime(end_time, "%H:%M:%S").time()

    return PicklistCleanerManagerConfig(
        start_time=start_time,
        end_time=end_time,
        days_to_keep_data=int(config.find_value("Production.DaysToKeepData", 45))
    )


def get_extra_config(group):
    # type: (cfgtools.Group) -> Dict[str, str]
    inner_dict = {}
    for key in group.keys:
        inner_dict[key.name] = key.values if len(key.values) > 1 else key.value()
    for inner_group in group.groups:
        inner_dict[inner_group.name] = get_extra_config(inner_group)
    return inner_dict


def build_logger(base_logger, name, level):
    # type: (Logger, str, int) -> Logger
    logger = logging.getLogger(name)
    for handler in base_logger.handlers:
        logger.addHandler(handler)
    logger.setLevel(level)

    return logger


def _get_connection_string(
    config,     # type: cfgtools.Group
    db_name,    # type: str
):
    # type: (...) -> str

    default_value = os.path.join("{HVDATADIR}", "databases")
    path = config.find_value("Production.DatabaseDir") or default_value
    databases_dir = sys_parsefilepath(
        path=path,
    )
    return os.path.join(databases_dir, "{}.db".format(db_name))


def _wait_persistence(
    message_bus_context,    # type: MBEasyContext
):
    # type: (...) -> None

    persistence.Driver().open(
        mbcontext=message_bus_context,
    ).close()
