import logging
from threading import Lock

from typing import (
    Dict,
    List,
    Optional,
    Tuple,
    Union,
)

from application.configurator import Configurator
from application.interactor import (
    CatalogUpdaterInteractor,
    MediaUpdaterInteractor,
    UserUpdaterInteractor,
    GetCatalogVersionToApplyInteractor,
    LoaderUpdaterInteractor,
    RemoteLoaderUpdaterInteractor,
    BinaryUpdaterInteractor,
    GetVersionsInteractor,
    ChromeUpdaterInteractor,
    HasPendingLoaderUpdateInteractor,
)
from application.manager import (
    CatalogUpdaterManager,
    UserUpdaterManager,
    LoaderUpdaterManager,
    BinaryUpdaterManager,
    MediaUpdaterManager,
)
from application.model import (
    ListenedTokens,
    ListenedEvents,
    UpdateType,
    AsyncUuidMessageProcessor,
    Updater,
)
from application.model.customexception import (
    DisabledFunctionality,
    NoUpdateFound,
    HasNoProductsImagesToDownload,
)
from application.processor import (
    CatalogUpdaterProcessor,
    MediaUpdaterProcessor,
    GetCatalogVersionToApplyProcessor,
    UserUpdaterProcessor,
    LoaderUpdaterProcessor,
    RemoteLoaderUpdaterProcessor,
    BinaryUpdaterProcessor,
    GetVersionsProcessor,
    GetBinaryUpdaterProcessor,
    ChromeUpdaterProcessor,
    HasPendingLoaderUpdateProcessor,
)
from application.repository.api import (
    CatalogUpdaterAPIRepository,
    MediaUpdaterAPIRepository,
    LoaderUpdaterAPIRepository,
    UserUpdaterAPIRepository,
    BinaryUpdaterAPIRepository,
)
from application.repository.pos import (
    CatalogUpdaterPOSRepository,
    CommonPOSRepository,
    BinaryUpdaterPOSRepository,
)
from application.service import (
    StartStopUpdaterService,
    ChromeUpdaterService,
    UpdaterService,
    LoaderUpdaterService,
    ApacheUpdaterService,
    MediaUpdaterService,
)
from application.util import LoggerUtil
from messagehandler import MessageHandlerBuilder
from messageprocessor import (
    MessageProcessorMessageHandler,
    DefaultMessageProcessorExecutorFactory,
)
from messageprocessorutil import LoggingProcessorCallback
from mwhelper import BaseRepository

from messagebus import Message, Event

logger = LoggerUtil.get_logger_name(__name__)


class ComponentMessageHandlerBuilder(MessageHandlerBuilder):
    def __init__(self, configurator, pos_repositories, common_pos_repository):
        # type: (Configurator, List[BaseRepository], CommonPOSRepository) -> None

        self.logger = logger
        self.configs = configurator.configs
        self.pos_repositories = pos_repositories
        self.common_pos_repository = common_pos_repository

        self.message_handler = None  # type: Union[None, MessageProcessorMessageHandler]
        self.updaters = {}  # type: Dict[str: Updater]
        self.working_on_update = Lock()
        self.binary_updater_interactor = None  # type: Optional[BinaryUpdaterInteractor]

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

        version_to_apply = ListenedTokens.TK_POS_GET_CATALOG_VERSION_TO_APPLY.value
        get_versions = ListenedTokens.TK_POS_UPDATER_GET_VERSIONS.value

        api_repository = CatalogUpdaterAPIRepository(self.logger, self.configs)
        pos_repository = self.pos_repositories[UpdateType.catalog]
        updater_service = UpdaterService(
            api_repository,
            pos_repository,
            self.common_pos_repository,
            self.working_on_update,
            self.configs
        )

        catalog_enabled = self.configs.updaters[UpdateType.catalog.name].enabled
        catalog_interactor = GetCatalogVersionToApplyInteractor(pos_repository, updater_service, catalog_enabled)
        get_catalog_version_to_apply_interactor = catalog_interactor

        get_version_repository = self.pos_repositories["GetVersions"]
        get_versions_interactor = GetVersionsInteractor(
            pos_repository=get_version_repository,
            path_to_catalog_json=self.configs.path_to_catalog_json,
            logger=self.logger,
        )

        has_pending_loader_update_interactor = HasPendingLoaderUpdateInteractor(
            pos_repository=self.pos_repositories[UpdateType.loader],
        )

        has_pending_loader_update_processor = HasPendingLoaderUpdateProcessor(
            interactor=has_pending_loader_update_interactor,
        )

        msg_processors = {
            version_to_apply: GetCatalogVersionToApplyProcessor(get_catalog_version_to_apply_interactor),
            get_versions: GetVersionsProcessor(get_versions_interactor)
        }

        binaries_repository = self.pos_repositories[UpdateType.binaries]

        media_updater_processor, media_updater_service = self._build_media_singletons(
            binaries_repository=binaries_repository,
        )
        catalog_updater_processor = self._build_catalog_singletons(
            pos_repo=pos_repository,
            updater_service=updater_service,
            api_repo=api_repository,
            media_updater_service=media_updater_service,
            binaries_repository=binaries_repository,
        )
        user_updater_processor = self._build_user_singletons(
            binaries_repository=binaries_repository,
        )
        apache_updater_service = self._build_apache_updater_singletons()
        loader_updater_processor, loader_remote_processor, loader_updater_service = self._build_loader_singletons(
            binaries_repository=binaries_repository,
        )
        chrome_updater_processor, chrome_updater_service = self._build_chrome_updater_singletons()
        start_stop_updater_service = self._build_start_stop_updater_singletons()
        binary_updater_processor, get_binary_update_processor = self._build_binary_singletons(
            binaries_repository=binaries_repository,
            loader_updater_service=loader_updater_service,
            chrome_updater_service=chrome_updater_service,
            start_stop_updater_service=start_stop_updater_service,
            apache_updater_service=apache_updater_service,
        )

        event_processors = {}
        if catalog_updater_processor:
            msg_processors[ListenedTokens.TK_POS_UPDATER_PERFORM_CATALOG_UPDATE.value] = catalog_updater_processor
        if media_updater_processor:
            msg_processors[ListenedTokens.TK_POS_UPDATER_PERFORM_MEDIA_UPDATE.value] = media_updater_processor
            event_processors[ListenedEvents.EVT_POS_UPDATER_PERFORM_MEDIA_UPDATE.value] = media_updater_processor
        if user_updater_processor:
            msg_processors[ListenedTokens.TK_POS_UPDATER_PERFORM_USER_UPDATE.value] = user_updater_processor
            event_processors[ListenedEvents.EVT_POS_UPDATER_PERFORM_USER_UPDATE.value] = user_updater_processor
        if loader_updater_processor:
            msg_processors[ListenedTokens.TK_POS_UPDATER_PERFORM_LOADER_UPDATE.value] = loader_updater_processor
            event_processors[ListenedEvents.EVT_POS_UPDATER_PERFORM_LOADER_UPDATE.value] = loader_updater_processor
        if loader_remote_processor:
            event_processors[ListenedEvents.EVT_POS_UPDATER_REMOTE_LOADER_UPDATE.value] = loader_remote_processor
        if binary_updater_processor:
            msg_processors[ListenedTokens.TK_POS_UPDATER_PERFORM_BINARY_UPDATE.value] = binary_updater_processor
            event_processors[ListenedEvents.EVT_POS_UPDATER_PERFORM_BINARY_UPDATE.value] = binary_updater_processor
        if get_binary_update_processor:
            msg_processors[ListenedTokens.TK_POS_HAS_BINARY_UPDATE_TO_APPLY.value] = get_binary_update_processor
        if chrome_updater_processor:
            msg_processors[ListenedTokens.TK_POS_UPDATER_PERFORM_CHROME_UPDATE.value] = chrome_updater_processor

        pending_loader_update_token = ListenedTokens.TK_POS_UPDATER_HAS_PENDING_LOADER_UPDATE.value
        msg_processors[pending_loader_update_token] = has_pending_loader_update_processor

        log_level_exceptions = {
            DisabledFunctionality: logging.WARN,
            NoUpdateFound: logging.INFO,
            HasNoProductsImagesToDownload: logging.INFO
        }
        callbacks = [LoggingProcessorCallback(self.logger, log_level_exceptions)]

        self.message_handler = MessageProcessorMessageHandler(
            event_processors,
            msg_processors,
            DefaultMessageProcessorExecutorFactory(callbacks)
        )

        self._start_managers()

    def _start_managers(self):
        for updater in self.updaters:
            if self.updaters[updater].manager:
                self.updaters[updater].manager.start()

    def build_message_handler(self, message, event):
        # type: (Message, Event) -> None

        return self.message_handler

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

        for updater in self.updaters:
            if self.updaters[updater].manager:
                self.updaters[updater].manager.stop()

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

        return

    def _build_catalog_singletons(
        self,
        pos_repo,               # type: CatalogUpdaterPOSRepository
        updater_service,        # type: UpdaterService
        api_repo,               # type: CatalogUpdaterAPIRepository
        media_updater_service,  # type: MediaUpdaterService
        binaries_repository,    # type: BinaryUpdaterPOSRepository
    ):
        # type: (...) -> Optional[CatalogUpdaterProcessor] # noqa

        update_type = UpdateType.catalog
        update_name = update_type.name

        if update_name not in self.configs.updaters:
            return

        local_configs = self.configs.updaters[update_name]
        interactor = CatalogUpdaterInteractor(self.configs, self.working_on_update, api_repo, pos_repo, updater_service,
                                              local_configs.enabled)
        processor = CatalogUpdaterProcessor(interactor)
        manager = CatalogUpdaterManager(
            configs=self.configs,
            interactor=interactor,
            api_repository=api_repo,
            pos_repository=pos_repo,
            updater_service=updater_service,
            media_updater_service=media_updater_service,
            binaries_repository=binaries_repository,
            common_pos_repository=self.common_pos_repository,
        )

        local_configs = self.configs.updaters[update_type.name]

        self.updaters[local_configs.name] = Updater(update_type=update_type,
                                                    manager=manager,
                                                    interactor=interactor)

        return processor

    def _build_media_singletons(
        self,
        binaries_repository,    # type: BinaryUpdaterPOSRepository
    ):
        # type: (...) -> Optional[Tuple[Union[AsyncUuidMessageProcessor, None], MediaUpdaterService]]

        update_type = UpdateType.media
        update_name = update_type.name

        if update_name not in self.configs.updaters:
            return

        update_type = UpdateType.media
        update_name = update_type.name

        if update_name not in self.configs.updaters:
            return

        local_configs = self.configs.updaters[update_name]
        api_repo = MediaUpdaterAPIRepository(self.configs)
        pos_repo = self.pos_repositories[UpdateType.media]
        enabled = local_configs.enabled

        updater_service = UpdaterService(
            api_repo,
            pos_repo,
            self.common_pos_repository,
            self.working_on_update,
            self.configs
        )

        media_updater_service = MediaUpdaterService(
            configs=self.configs,
            working_on_update=self.working_on_update,
            api_repository=api_repo,
            pos_repository=pos_repo,
            updater_service=updater_service,
            enabled=enabled,
        )

        local_configs = self.configs.updaters[update_name]
        interactor = MediaUpdaterInteractor(media_updater_service=media_updater_service)
        processor = MediaUpdaterProcessor(interactor)
        manager = MediaUpdaterManager(
            self.configs,
            interactor,
            pos_repo,
            binaries_repository,
        )

        self.updaters[local_configs.name] = Updater(update_type, manager, interactor)

        return processor, media_updater_service

    def _build_user_singletons(
        self,
        binaries_repository,    # type: BinaryUpdaterPOSRepository
    ):
        # type: (...) -> Union[AsyncUuidMessageProcessor, None]

        update_type = UpdateType.user
        update_name = update_type.name

        if update_name not in self.configs.updaters:
            return

        local_configs = self.configs.updaters[update_name]
        api_repo = UserUpdaterAPIRepository(self.configs)
        pos_repo = self.pos_repositories[UpdateType.user]
        enabled = local_configs.enabled
        interactor = UserUpdaterInteractor(self.configs, self.working_on_update, api_repo, pos_repo, enabled)
        processor = UserUpdaterProcessor(interactor)
        manager = UserUpdaterManager(
            self.configs,
            interactor,
            pos_repo,
            binaries_repository,
        )

        self.updaters[local_configs.name] = Updater(
            update_type=update_type,
            manager=manager,
            interactor=interactor
        )

        return processor

    def _build_loader_singletons(
        self,                 # type: ComponentMessageHandlerBuilder
        binaries_repository,  # type: BinaryUpdaterPOSRepository
    ):
        # type: (...) -> (Union[AsyncUuidMessageProcessor, None], LoaderUpdaterService)

        update_type = UpdateType.loader
        update_name = update_type.name

        if update_name not in self.configs.updaters:
            return

        pos_repo = self.pos_repositories[UpdateType.loader]
        api_repo = LoaderUpdaterAPIRepository(
            logger=logger,
            configs=self.configs,
        )
        updater_service = UpdaterService(
            api_repo,
            pos_repo,
            self.common_pos_repository,
            self.working_on_update,
            self.configs
        )

        catalog_repo = self.pos_repositories[UpdateType.catalog]
        version_repo = self.pos_repositories["GetVersions"]

        loader_updater_service = LoaderUpdaterService(
            self.configs,
            self.working_on_update,
            api_repo,
            pos_repo,
            version_repo,
            updater_service
        )
        remote_updater_interactor = RemoteLoaderUpdaterInteractor(
            loader_updater_service=loader_updater_service,
            remote_update_window=self.configs.updaters.get("loader").remote_update_window,
            working_on_update=self.working_on_update,
            pos_repository=pos_repo,
        )
        remote_updater_processor = RemoteLoaderUpdaterProcessor(interactor=remote_updater_interactor)
        loader_updater_interactor = LoaderUpdaterInteractor(loader_updater_service=loader_updater_service)
        loader_updater_processor = LoaderUpdaterProcessor(interactor=loader_updater_interactor)
        manager = LoaderUpdaterManager(
            configs=self.configs,
            interactor=loader_updater_interactor,
            pos_repository=pos_repo,
            catalog_pos_repository=catalog_repo,
            binaries_repository=binaries_repository,
            loader_updater_service=loader_updater_service,
        )

        local_configs = self.configs.updaters[update_name]
        self.updaters[local_configs.name] = Updater(
            update_type=update_type,
            manager=manager,
            interactor=loader_updater_interactor,
        )

        return loader_updater_processor, remote_updater_processor, loader_updater_service

    def _build_apache_updater_singletons(self):
        # type: () -> ApacheUpdaterService

        pos_repo = self.pos_repositories[UpdateType.binaries]
        updater_service = UpdaterService(
            api_repository=None,
            pos_repository=pos_repo,
            common_repository=self.common_pos_repository,
            working_on_update=self.working_on_update,
            configs=self.configs,
        )

        return ApacheUpdaterService(updater_service=updater_service)

    def _build_chrome_updater_singletons(
        self
    ):
        # type: () -> ChromeUpdaterProcessor

        pos_repo = self.pos_repositories[UpdateType.binaries]
        updater_service = UpdaterService(
            api_repository=None,
            pos_repository=pos_repo,
            common_repository=self.common_pos_repository,
            working_on_update=self.working_on_update,
            configs=self.configs
        )

        chrome_updater_service = ChromeUpdaterService(
            configs=self.configs,
            working_on_update=self.working_on_update,
            updater_service=updater_service,
            pos_repository=pos_repo
        )

        chrome_updater_interactor = ChromeUpdaterInteractor(
            chrome_updater_service=chrome_updater_service
        )

        return ChromeUpdaterProcessor(chrome_updater_interactor), chrome_updater_service

    def _build_start_stop_updater_singletons(
        self
    ):
        # type: () -> StartStopUpdaterService

        pos_repo = self.pos_repositories[UpdateType.binaries]
        updater_service = UpdaterService(
            api_repository=None,
            pos_repository=pos_repo,
            common_repository=self.common_pos_repository,
            working_on_update=self.working_on_update,
            configs=self.configs,
        )

        start_stop_updater_service = StartStopUpdaterService(updater_service=updater_service)

        return start_stop_updater_service

    def _build_binary_singletons(
        self,
        binaries_repository,            # type: BinaryUpdaterPOSRepository
        loader_updater_service,         # type: LoaderUpdaterService
        chrome_updater_service,         # type: ChromeUpdaterService
        start_stop_updater_service,     # type: StartStopUpdaterService
        apache_updater_service,         # type: ApacheUpdaterService
    ):
        # type: (...) -> Union[AsyncUuidMessageProcessor, None]

        update_type = UpdateType.binaries
        update_name = update_type.name

        if update_name not in self.configs.updaters:
            return

        api_repo = BinaryUpdaterAPIRepository(self.configs)
        updater_service = UpdaterService(
            api_repo,
            binaries_repository,
            self.common_pos_repository,
            self.working_on_update,
            self.configs
        )
        interactor = BinaryUpdaterInteractor(
            configs=self.configs,
            working_on_update=self.working_on_update,
            api_repository=api_repo,
            pos_repository=binaries_repository,
            updater_service=updater_service,
            loader_updater_service=loader_updater_service,
            chrome_updater_service=chrome_updater_service,
            start_stop_updater_service=start_stop_updater_service,
            apache_updater_service=apache_updater_service,
        )
        self.binary_updater_interactor = interactor
        self.binary_updater_interactor.update_binaries_start()
        processor = BinaryUpdaterProcessor(interactor)
        manager = BinaryUpdaterManager(
            self.configs,
            interactor,
            binaries_repository
        )
        has_update_processor = GetBinaryUpdaterProcessor(interactor)
        local_configs = self.configs.updaters[update_name]
        self.updaters[local_configs.name] = Updater(update_type, manager, interactor)

        return processor, has_update_processor
