from datetime import (
    datetime,
    timedelta,
)
from random import randint
from typing import Optional

from application.interactor import LoaderUpdaterInteractor
from application.manager._UpdaterManager import UpdaterManager
from application.model import UpdateType
from application.model.configuration import Configurations
from application.model.customexception import NoUpdateFound
from application.repository.pos import (
    LoaderUpdaterPOSRepository,
    CatalogUpdaterPOSRepository,
    BinaryUpdaterPOSRepository,
    CommonPOSRepository,
)
from application.service import LoaderUpdaterService
from application.util import LoggerUtil

logger = LoggerUtil.get_logger_name(__name__)


class LoaderUpdaterManager(UpdaterManager):
    def __init__(
        self,
        configs,                    # type: Configurations
        interactor,                 # type: LoaderUpdaterInteractor
        pos_repository,             # type: LoaderUpdaterPOSRepository
        catalog_pos_repository,     # type: CatalogUpdaterPOSRepository
        binaries_repository,        # type: BinaryUpdaterPOSRepository
        loader_updater_service,     # type: LoaderUpdaterService
        common_pos_repository,      # type: CommonPOSRepository
    ):
        # type: (...) -> None # noqa

        name = UpdateType.loader.name
        super(LoaderUpdaterManager, self).__init__(name, configs, interactor, pos_repository, logger)
        self.interactor = interactor
        self.catalog_pos_repository = catalog_pos_repository
        self.binaries_repository = binaries_repository
        self.configs = configs
        self.pos_repository = pos_repository
        self.loader_updater_service = loader_updater_service
        self.common_pos_repository = common_pos_repository

        self.update_value = UpdateType.loader.value
        self.logger = logger

        try:
            if not self.has_already_applied_update():
                self.has_update_to_apply()
                self.update()
        except (Exception,):
            self.logger.exception("Error updating loader on manager")

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

        self.interactor.update_loaders()

    def has_update_to_apply(
        self,   # type: LoaderUpdaterManager
    ):
        # type: (...) -> None

        if not self.binaries_repository.has_already_applied_update():
            raise NoUpdateFound()

        if not self.catalog_pos_repository.has_already_applied_update():
            raise NoUpdateFound()

        last_update_date = self.pos_repository.get_last_loader_update_date()
        today = datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
        update_to_apply = last_update_date is None or last_update_date < today
        if not update_to_apply:
            raise NoUpdateFound()

    def notify_pending_update(
        self,   # type: LoaderUpdaterManager
    ):
        # type: (...) -> None

        has_pending_notify = self.pos_repository.has_pending_loader_update_to_notify()
        if not has_pending_notify:
            return

        last_loader_update = self.pos_repository.get_last_loader_update()
        if last_loader_update is None:
            return

        self.loader_updater_service.send_result_message(
            current_update_id=str(last_loader_update.update_id),
        )

    def get_pending_remote_update_id(
        self,   # type: LoaderUpdaterManager
    ):
        # type: (...) -> Optional[str]

        return self.pos_repository.get_pending_loader_external_update_id()

    def get_pending_remote_update_to_schedule(
        self,                  # type: LoaderUpdaterManager
        external_update_id,    # type: Optional[str]
    ):
        # type: (...) -> Optional[datetime.time]

        update_time = self._get_random_time()
        if not update_time:
            return

        if not external_update_id or external_update_id in self.update_scheduled:
            return

        self.update_scheduled[external_update_id] = update_time
        return update_time

    def has_already_applied_update(self):
        # type: () -> bool

        return self.common_pos_repository.has_already_applied_update(self.configs.persistence_name, self.update_value)

    def _get_random_time(
        self,   # type: LoaderUpdaterManager
    ):
        # type: (...) -> Optional[datetime.time]

        loader = self.configs.updaters.get("loader")
        if not getattr(loader, "remote_update_window", None):
            return

        start_datetime = self._to_datetime(time_to_format=loader.remote_update_window.start_time)
        end_datetime = self._to_datetime(time_to_format=loader.remote_update_window.end_time)
        return self._get_random_time_interval(
            start_datetime=start_datetime,
            end_datetime=end_datetime,
        )

    @staticmethod
    def _to_datetime(
        time_to_format,   # type: datetime.time
    ):
        # type: (...) -> datetime

        return datetime.combine(datetime.today(), time_to_format)

    @staticmethod
    def _get_random_time_interval(
        start_datetime,    # type: datetime
        end_datetime,      # type: datetime
    ):
        # type: (...) -> datetime.time

        today = datetime.today()
        if start_datetime.time() < today.time() < end_datetime.time():
            start_datetime = today

        time_interval = (end_datetime - start_datetime).seconds
        random_second = randint(0, time_interval)
        update_time = start_datetime + timedelta(seconds=random_second)

        if update_time < today:
            return update_time + timedelta(days=1)

        return update_time
