from abc import ABCMeta, abstractmethod
from logging import Logger

from application.interactor import UserUpdaterInteractor, MediaUpdaterInteractor, LoaderUpdaterInteractor, \
    CatalogUpdaterInteractor, BinaryUpdaterInteractor
from application.model import BaseThread
from application.model.configuration import Configurations
from application.model.customexception import NotInsideUpdateWindow, NotImplementedException, NoUpdateFound, \
    DisabledFunctionality
from application.repository.pos import UserUpdaterPOSRepository, MediaUpdaterPOSRepository, \
    LoaderUpdaterPOSRepository, CatalogUpdaterPOSRepository, BinaryUpdaterPOSRepository
from application.util import ValidationUtil
from typing import Union, Optional


class UpdaterManager(BaseThread):
    __metaclass__ = ABCMeta

    def __init__(self, update_name, configs, interactor, pos_repository, logger):
        # type: (str, Configurations, Union[UserUpdaterInteractor, MediaUpdaterInteractor, LoaderUpdaterInteractor, CatalogUpdaterInteractor, BinaryUpdaterInteractor], Union[UserUpdaterPOSRepository, MediaUpdaterPOSRepository, LoaderUpdaterPOSRepository, CatalogUpdaterPOSRepository, BinaryUpdaterPOSRepository], Logger) -> None # noqa

        super(UpdaterManager, self).__init__()
        self.update_name = update_name
        self.local_configs = configs.updaters[self.update_name]
        self.active_frequency = int(self.local_configs.active_frequency)
        self.interactor = interactor
        self.pos_repository = pos_repository
        self.logger = logger

    def run(self, validate_window=True):
        # type: (Optional[bool]) -> None

        if self.active_frequency == 0:
            self.logger.info("{} - Auto-update thread not active. Stopping manager".format(self.update_name))
            self.stop()
            return

        self.logger.info("{} - update thread manager started".format(self.update_name))

        self._get_update(validate_window)

    def _get_update(self, validate_window):
        # type: (bool) -> None

        disabled_update = False
        while self.running:
            try:
                self._check_update_window(validate_window)
                self.has_update_to_apply()

                self.update()
            except DisabledFunctionality as _:
                self.logger.info("{} - Auto-update thread disabled. Stopping manager".format(self.update_name))
                disabled_update = True
                self.stop()
                return
            except NotInsideUpdateWindow as _:
                self.logger.info("{} - Not inside update windows".format(self.update_name))
            except NoUpdateFound as _:
                self.logger.info("{} - No update found".format(self.update_name))
            except Exception as _:
                self.logger.exception("{} - Not expected exception on update".format(self.update_name))
            finally:
                if not disabled_update:
                    if not self.pos_repository.has_already_applied_update():
                        self.sleep(30)
                    else:
                        self.sleep(self.active_frequency)

    @abstractmethod
    def update(self):
        # type: () -> None

        raise NotImplementedException()

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

        return

    def _check_update_window(self, validate_window):
        # type: (bool) -> None

        if not validate_window or not self.pos_repository.has_already_applied_update():
            return

        start_time = self.local_configs.window.start_time
        end_time = self.local_configs.window.end_time
        if not ValidationUtil().is_inside_allowed_window(start_time, end_time):
            str_start_time = start_time.strftime("%H:%M:%S")
            str_end_time = end_time.strftime("%H:%M:%S")
            message = "Not inside allowed window. Start: {} / End: {}".format(str_start_time, str_end_time)
            self.logger.info(message)
            raise NotInsideUpdateWindow()
