import json
import os
import hashlib
import threading
import time
from datetime import datetime
from threading import Lock
from typing import (
    Dict,
    Optional,
    Tuple,
    List,
)

from application.model import UpdateType
from application.model.configuration import Configurations
from application.model.customexception import (
    NotExpectedException,
    DisabledFunctionality,
    ErrorCreatingBackup,
    ErrorDeletingContent,
    RollbackError,
    NotInsideUpdateWindow,
    InvalidDownloadUpdate,
    NoUpdateFound,
    ErrorDownloadingLoaders,
    ErrorValidatingOpenedUsers,
)
from application.repository.api import BinaryUpdaterAPIRepository
from application.repository.pos import BinaryUpdaterPOSRepository
from application.service import (
    ApacheUpdaterService,
    StartStopUpdaterService,
    ChromeUpdaterService,
    LoaderUpdaterService,
    UpdaterService,
    CommonService,
)
from application.util import LoggerUtil, ValidationUtil, settings
from systools import sys_parsefilepath
from packaging import version

GENESIS_PATH = os.path.join("..", "..", "genesis")
SRC_PATH = os.path.join("..", "..", "src")

logger = LoggerUtil.get_logger_name(__name__)


class BinaryUpdaterInteractor(object):
    def __init__(
        self,
        configs,                     # type: Configurations
        working_on_update,           # type: Lock
        api_repository,              # type: BinaryUpdaterAPIRepository
        pos_repository,              # type: BinaryUpdaterPOSRepository
        updater_service,             # type: UpdaterService
        loader_updater_service,      # type: LoaderUpdaterService
        chrome_updater_service,      # type: ChromeUpdaterService
        start_stop_updater_service,  # type: StartStopUpdaterService
        apache_updater_service,      # type: ApacheUpdaterService
    ):
        # type: (...) -> None # noqa

        self.update_type = UpdateType.binaries
        self.update_name = self.update_type.name

        self.enabled = configs.updaters[self.update_name].enabled
        self.local_configs = configs.updaters[self.update_name]
        self.start_time = configs.updaters[self.update_name].apply_window.start_time
        self.end_time = configs.updaters[self.update_name].apply_window.end_time
        self.store_id = configs.store_id
        self.force_update_days = configs.updaters[self.update_name].force_update_days
        self.restrict_days_update = configs.updaters[self.update_name].restrict_days_update

        self.working_on_update = working_on_update
        self.api_repository = api_repository
        self.pos_repository = pos_repository
        self.updater_service = updater_service
        self.loader_updater_service = loader_updater_service

        data_path = sys_parsefilepath("{HVDATADIR}")
        work_dir = sys_parsefilepath(configs.backup_directory)
        self.htdocs_relative_path = "htdocs"
        self.htdocs_path = os.path.join(data_path, GENESIS_PATH, 'data', 'server', self.htdocs_relative_path)
        self.genesis_path = os.path.join(data_path, GENESIS_PATH)
        self.renamed_htdocs_path = self.htdocs_path + "_PRE_UPDATE"
        self.src_path = os.path.join(data_path, SRC_PATH)
        self.genesis_src_path = os.path.join(data_path, GENESIS_PATH, "src")
        self.renamed_src_path = self.genesis_src_path + "_PRE_UPDATE"
        self.genesis_bin_path = os.path.join(data_path, GENESIS_PATH, "bin")
        self.core_relative_linux_path = "linux-x86_64"
        self.core_linux_path = os.path.join(data_path, GENESIS_PATH, "bin", self.core_relative_linux_path)
        self.renamed_core_linux_path = self.core_linux_path + "_PRE_UPDATE"
        self.core_relative_windows_path = "windows-x86"
        self.core_windows_path = os.path.join(data_path, GENESIS_PATH, "bin", self.core_relative_windows_path)
        self.core_path = os.path.join(data_path, GENESIS_PATH, "bin")
        self.renamed_core_windows_path = self.core_windows_path + "_PRE_UPDATE"
        default_work_path = os.path.join(work_dir, self.update_type.name)
        self.downloads_path = os.path.join(default_work_path, "downloads")
        self.backups_path = os.path.join(default_work_path, "backups")
        self.work_path = os.path.join(default_work_path, "work_path")
        self.binary_src_zip = "binary_src.tar.gz"
        self.binary_core_zip = "binary_core.tar.gz"
        self.core_linux_backup = ''
        self.core_windows_backup = ''
        self.src_backup = ''
        self.htdocs_backup = ''
        self.python3_path = os.path.join(data_path, "..", "..", "python3")

        self.updater_service.create_dir(self.backups_path, False)
        self.updater_service.create_dir(self.downloads_path, False)
        self.apache_updater_service = apache_updater_service
        self.chrome_updater_service = chrome_updater_service
        self.start_stop_updater_service = start_stop_updater_service

    def update_binaries_start(self):
        result = self.pos_repository.get_last_binary_update_not_applied_controller_id()
        downloaded_date = result.get("downloaded_date", None)
        if downloaded_date is None:
            return

        diff_days = (datetime.now() - downloaded_date).days
        if self.force_update_days == 0 or (diff_days > self.force_update_days):
            t1 = threading.Thread(target=self.update_binaries, args=("updateNow", True))
            t1.start()

    def update_binaries(
        self,
        data,           # type: Optional[str]
        silent=False    # type: bool
    ):
        # type: (...) -> None

        settings.set_update_control(self.update_name, None)
        if not self.enabled:
            settings.set_update_control(self.update_name, "$UPDATE_DISABLED")
            raise DisabledFunctionality()

        rollback_necessary = False
        try:
            self.updater_service.create_dir(self.core_linux_path, False)
            self.updater_service.create_dir(self.core_windows_path, False)

            settings.set_update_control(self.update_name, "$UPDATING_BINARIES")
            settings.set_update_control("updating", not silent)
            backoffice_version = self.api_repository.check_last_update_binaries()

            core, core_version, src, src_version = self._has_update_to_apply(backoffice_version=backoffice_version)
            if self.pos_repository.has_already_applied_update() and silent and not self._can_update_in_current_day():
                settings.set_update_control(self.update_name, "$UPDATE_DISABLED_FOR_CURRENT_DAY")
                raise DisabledFunctionality()

            self.loader_updater_service.download_and_validate_loaders()

            logger.info("Waiting Updating Binaries")
            with self.working_on_update:
                core_new_version = backoffice_version["coreVersionNumber"]
                src_new_version = backoffice_version["srcVersionNumber"]

                file_src_path = os.path.join(self.downloads_path, src_new_version + self.binary_src_zip)
                file_core_path = os.path.join(self.downloads_path, core_new_version + self.binary_core_zip)
                result = self.pos_repository.get_last_binary_update_not_applied_controller_id()
                update_id = result.get("update_id", 0)
                downloaded = self._download_performed(
                    update_id=update_id,
                    backoffice_version=backoffice_version,
                    file_src_path=file_src_path,
                    file_core_path=file_core_path,
                )
                if not downloaded:
                    update_id = self._perform_download(
                        backoffice_version,
                        core_new_version,
                        file_core_path,
                        file_src_path,
                        src_new_version
                    )

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

                settings.set_update_control("updating", True)
                self.updater_service.create_dir(self.work_path)

                self._create_backups(update_id)

                rollback_necessary = True
                if core:
                    self._apply_binary_core(file_core_path)

                if src:
                    self._apply_binary_src(file_src_path)

                rollback_necessary = False
                self.pos_repository.set_update_as_applied(
                    update_id=update_id,
                    backoffice_version=backoffice_version,
                )
            try:
                self.chrome_updater_service.update_chrome(
                    validate_version=True,
                    restart_hv=False
                )
            except (Exception,):
                logger.exception("Error updating chrome after Binaries update")

            try:
                self.loader_updater_service.update_loaders(
                    restart_hv=False,
                    retry_on_error=True
                )
            except (Exception,):
                logger.exception("Error updating loader after Binaries update")

            try:
                self.apache_updater_service.update_httpd_conf()
            except (Exception,):
                logger.exception("Error updating httpd.conf after Binaries update")

            try:
                self.start_stop_updater_service.update_start_stop()
            except (Exception,):
                logger.exception("Error updating start/stop files after Binaries update")

            self.updater_service.create_dir(self.downloads_path, True)
            logger.info("Starting Hypervisor to apply new Binary")
            self.pos_repository.restart_hv()
            time.sleep(5)

            logger.info("Binaries update fully concluded with success!")
        except NotInsideUpdateWindow:
            settings.set_update_control(self.update_name, "$NOT_INSIDE_UPDATE_WINDOW")
            logger.info("Sleeping until enter the update window")
            raise
        except ErrorValidatingOpenedUsers:
            settings.set_update_control(self.update_name, "$UPDATE_BINARIES_ERRORVALIDATINGOPENEDUSERS")
            logger.info("Error validating loaders. Necessary to close all the operators and the day")
            raise
        except NoUpdateFound:
            logger.info("No Binaries update found to apply")
            settings.set_update_control(self.update_name, "$SUCCESS_UPDATE")
            raise
        except (NotExpectedException, Exception) as ex:
            if rollback_necessary:
                with self.working_on_update:
                    self._rollback()

            if isinstance(ex, ErrorDownloadingLoaders):
                settings.set_update_control(self.update_name, ex.message)
            else:
                settings.set_update_control(self.update_name, "$UNKNOWN_ERROR")

            logger.exception("Not expected exception on binaries update")
            raise
        finally:
            self.updater_service.delete_directory(self.work_path)
            settings.set_update_control("updating", False)

    def _remove_non_applied_updates(self):
        update_id = 1
        while update_id != 0:
            result = self.pos_repository.get_last_binary_update_not_applied_controller_id()
            update_id = result.get("update_id", 0)
            if update_id != 0:
                self.pos_repository.remove_downloaded_update(update_id)

    def _has_update_to_apply(
        self,                   # type: BinaryUpdaterInteractor
        backoffice_version,     # type: Dict[str, str]
    ):
        # type: (...) -> Tuple[bool, str, bool, str]

        core_version, src_version = self._check_last_update_binaries()
        core_new_version = backoffice_version["coreVersionNumber"]
        core = self._check_if_update_is_different(core_version, core_new_version)
        src_new_version = backoffice_version["srcVersionNumber"]
        src = self._check_if_update_is_different(src_version, src_new_version)
        if not core and not src:
            logger.info("No update found")
            raise NoUpdateFound()

        return core, core_version, src, src_version

    def notify_update(
        self,       # type: BinaryUpdaterInteractor
    ):
        # type: (...) -> None

        update_data = self.pos_repository.get_last_binary_update_info()
        if not update_data.applied_date or update_data.notified_date:
            return

        server_update_identifier = self._get_server_update_identifier(update_data)
        if not server_update_identifier:
            return

        src_version = server_update_identifier.get("srcVersionNumber")
        src_pkg_info_file_version = self._get_pkg_info_file_version(
            pkg_info_file_path=os.path.join(self.src_path, "PKG-INFO")
        )
        if src_pkg_info_file_version is not None and src_version and src_pkg_info_file_version != src_version:
            return

        core_version = server_update_identifier.get("coreVersionNumber")
        core_pkg_info_file_version = self._get_pkg_info_file_version(
            pkg_info_file_path=os.path.join(self.core_path, "PKG-INFO")
        )
        if core_pkg_info_file_version is not None and core_version and core_pkg_info_file_version != core_version:
            return

        update_version_id = server_update_identifier.get("id")
        update_notified = self.api_repository.notify_update(
            version_id=str(update_version_id),
        )
        if update_notified:
            self.pos_repository.set_notified_date(
                update_id=str(update_data.update_id),
            )
            self._remove_non_applied_updates()

    def _get_server_update_identifier(self, update_data):
        server_update_identifier = json.loads(update_data.server_update_identifier or "{}")
        if server_update_identifier:
            return server_update_identifier

        backoffice_version = self.api_repository.check_last_update_binaries()
        backoffice_src_version = backoffice_version["srcVersionNumber"]
        database_src_version = update_data.update_name.split("|")[1].replace("SRC:", "")
        if backoffice_src_version != database_src_version:
            return {}

        return {
            "srcVersionNumber": backoffice_src_version,
            "id": backoffice_version["id"],
        }

    @staticmethod
    def _get_pkg_info_file_version(
        pkg_info_file_path,     # type: str
    ):
        # type: (...) -> Optional[str]

        if os.path.isfile(pkg_info_file_path):
            with open(pkg_info_file_path, 'rb') as f:
                lines = f.readlines()
                for line in lines:
                    if line.startswith("Version:"):
                        return line.split("Version:")[1].strip()

    def _create_backups(self, update_id):
        settings.set_update_control(self.update_name, "$CREATING_POS_BACKUP")
        self.core_windows_backup = self._create_backup(self.core_windows_path, "core_windows")
        self.core_linux_backup = self._create_backup(self.core_linux_path, "core_linux")
        self.src_backup = self._create_backup(self.genesis_src_path, "src")
        self.htdocs_backup = self._create_backup(self.htdocs_path, "htdocs")
        self.pos_repository.set_backup_date(update_id)

    def _perform_download(self, backoffice_version, core_new_version, file_core_path, file_src_path, src_new_version):
        logger.info("Updating Binaries")
        new_update_id = str(self.pos_repository.get_last_binary_update_id() + 1)
        update_name = "CORE:{}|SRC:{}".format(core_new_version, src_new_version)
        self.pos_repository.insert_new_binary_update(new_update_id, update_name)

        update_data = self.pos_repository.get_last_binary_update_info()
        logger.info("Downloading src")
        settings.set_update_control(self.update_name, "$DOWNLOADING_SRC_FROM_BACKOFFICE")
        self.api_repository.download_src(backoffice_version["srcVersionLink"], file_src_path)
        logger.info("SRC downloaded")

        logger.info("Downloading core")
        settings.set_update_control(self.update_name, "$DOWNLOADING_CORE_FROM_BACKOFFICE")
        self.api_repository.download_core(backoffice_version["coreVersionLink"], file_core_path)
        logger.info("CORE downloaded")

        hash_src = self._get_hash(file_src_path)
        hash_core = self._get_hash(file_core_path)
        src_version_hash = backoffice_version["srcVersionHash"]
        core_version_hash = backoffice_version["coreVersionHash"]

        if hash_src != src_version_hash or hash_core != core_version_hash:
            logger.info("POS srcVersionHash: {}".format(hash_src))
            logger.info("BackOffice srcVersionHash: {}".format(src_version_hash))
            logger.info("POS coreVersionHash: {}".format(hash_core))
            logger.info("BackOffice coreVersionHash: {}".format(core_version_hash))

            self.api_repository.delete_file(file_src_path)
            self.api_repository.delete_file(file_core_path)
            raise InvalidDownloadUpdate()

        file_src_hash_path = os.path.join(self.downloads_path, src_new_version + "-" + src_version_hash)
        self.api_repository.save_file(file_src_hash_path, "")
        file_core_hash_path = os.path.join(self.downloads_path, core_new_version + "-" + core_version_hash)
        self.api_repository.save_file(file_core_hash_path, "")

        self.pos_repository.set_downloaded_date(update_data.update_id)
        return update_data.update_id

    def _apply_binary_core(self, file_core_path):
        logger.info("Applying core")
        settings.set_update_control(self.update_name, "$APPLYING_CORE")

        un_gz_folder = os.path.join(self.work_path, "ungz")
        self.updater_service.create_dir(un_gz_folder)
        un_gz_folder = os.path.join(un_gz_folder, "core")
        self.updater_service.create_dir(un_gz_folder)

        self.updater_service.extract_tar_gz(file_core_path, un_gz_folder)
        core_update_folder_name = os.listdir(un_gz_folder)[0]

        for folder in ["apache", "bin", "python", "python3"]:
            for so in [self.core_relative_linux_path, self.core_relative_windows_path]:
                genesis_folder = os.path.join(self.genesis_path, folder)
                self.updater_service.create_dir(genesis_folder, False)
                genesis_so_path = os.path.join(genesis_folder, so)
                self.updater_service.create_dir(genesis_so_path)
                so_update_path = os.path.join(un_gz_folder, core_update_folder_name, "pos-core", so, folder)
                self.updater_service.copy_all_content(so_update_path, genesis_so_path)

        self._update_pkg_info_file(
            file_path=un_gz_folder,
            genesis_file=core_update_folder_name,
            destination_paths=[self.core_linux_path, self.core_windows_path],
        )

        logger.info("Core applied")

    def _apply_binary_src(self, file_src_path):
        logger.info("Applying src")
        settings.set_update_control(self.update_name, "$APPLYING_SRC")

        if os.path.exists(self.genesis_src_path):
            self.updater_service.rename_folder(self.genesis_src_path, self.renamed_src_path)

        if os.path.exists(self.htdocs_path):
            self.updater_service.rename_folder(self.htdocs_path, self.renamed_htdocs_path)

        self.updater_service.create_dir(self.htdocs_path)

        ungz_folder = os.path.join(self.work_path, "ungz")
        self.updater_service.create_dir(ungz_folder)
        ungz_folder = os.path.join(ungz_folder, "src")
        self.updater_service.create_dir(ungz_folder)

        self.updater_service.extract_tar_gz(file_src_path, ungz_folder)
        src_update_folder_name = os.listdir(ungz_folder)[0]

        temp_path = os.path.join(ungz_folder, src_update_folder_name, "pos-src", "_comps")
        self.updater_service.move_directory(temp_path, self.genesis_src_path)

        gui_zip_folder = os.path.join(ungz_folder, src_update_folder_name, "pos-src", "_guizip")
        self.updater_service.extract_zip(os.path.join(gui_zip_folder, "sui.zip"), os.path.join(self.htdocs_path, "sui"))
        self.updater_service.extract_zip(os.path.join(gui_zip_folder, "kui.zip"), os.path.join(self.htdocs_path, "kui"))
        self.updater_service.extract_zip(os.path.join(gui_zip_folder, "nti.zip"), os.path.join(self.htdocs_path, "nti"))

        temp_path = os.path.join(ungz_folder, src_update_folder_name, "pos-src", "_pypkg")
        self.updater_service.copy_all_files(temp_path, self.core_windows_path)
        self.updater_service.copy_all_files(temp_path, self.core_linux_path)

        self._update_pkg_info_file(
            file_path=ungz_folder,
            genesis_file=src_update_folder_name,
            destination_paths=[self.genesis_src_path],
        )

        self.updater_service.delete_directory(self.renamed_src_path)
        self.updater_service.delete_directory(self.renamed_htdocs_path)

        logger.info("Src applied")

    def _update_pkg_info_file(
        self,                   # type: BinaryUpdaterInteractor
        file_path,              # type: str
        genesis_file,           # type: str
        destination_paths,      # type: List[str]
    ):
        # type: (...) -> None

        try:
            pkg_info_file = os.path.join(file_path, genesis_file, "PKG-INFO")
            if not os.path.isfile(pkg_info_file):
                logger.info("No PKG-INFO file found")
                return

            for path in destination_paths:
                self.updater_service.copy_file(
                    original_file_path=pkg_info_file,
                    destination_file_path=path,
                )
        except (Exception,):
            logger.exception("Error updating PKG-INFO file")

    def _check_last_update_binaries(self):
        last_update = self.pos_repository.get_last_binary_update_name()
        core_version = last_update.split('|')[0].split(':')[1] if last_update else ""
        src_version = last_update.split('|')[1].split(':')[1] if last_update else ""

        return core_version, src_version

    @staticmethod
    def _check_if_update_is_different(last_update, new_update):
        return version.parse(new_update) > version.parse(last_update)

    @staticmethod
    def _get_hash(file_path):
        # BUF_SIZE is totally arbitrary, change for your app!
        buf_size = 65536  # lets read stuff in 64kb chunks!

        sha256 = hashlib.sha256()

        with open(file_path, 'rb') as f:
            while True:
                data = f.read(buf_size)
                if not data:
                    break
                sha256.update(data)

        return sha256.hexdigest()

    def _create_backup(self, path_to_backup, update_type):
        # type: (str, str) -> str

        try:
            logger.info("Creating {} backup".format(update_type))

            backup_zip_name = "binaries_{}_{}".format(update_type, datetime.now().strftime("%Y%m%d_%H%M%S"))
            backup_package = os.path.join(self.backups_path, backup_zip_name)
            backup_package_zip = os.path.join(self.backups_path, backup_zip_name) + ".zip"

            zip_path = os.path.join(self.work_path, update_type)
            if os.path.exists(path_to_backup):
                self.updater_service.copy_directory(path_to_backup, zip_path)
                self.updater_service.save_directory_zip(zip_path, backup_package)
                self.updater_service.test_zip_file(backup_package_zip)
                self._clean_backups()

            logger.info("Backup successfully created for {}!".format(update_type))
            return backup_package_zip
        except (Exception,):
            logger.exception("Error creating {} backup".format(update_type))
            raise ErrorCreatingBackup()

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

        list_of_files = os.listdir(self.backups_path)

        while len(list_of_files) > 16:
            full_path = [os.path.join(self.backups_path, x) for x in list_of_files]
            oldest_file = min(full_path, key=os.path.getctime)
            if os.path.isfile(oldest_file):
                self.updater_service.delete_file(oldest_file)
            else:
                self.updater_service.delete_directory(oldest_file)

            list_of_files = os.listdir(self.backups_path)

    def _delete_content(self, path):
        # type: (str) -> None

        try:
            logger.info("Deleting path {}".format(path))

            self.updater_service.delete_directory(path)

            logger.info("Path deleted")
        except (Exception,):
            logger.exception("Error deleting path")
            raise ErrorDeletingContent()

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

        logger.info("Starting rollback")

        retries = 0
        max_retries = 2

        while True:
            try:
                if self.core_windows_backup != '':
                    self._delete_content(self.core_windows_path)
                    self._delete_content(self.renamed_core_windows_path)
                    self.updater_service.extract_zip(self.core_windows_backup, self.core_windows_path)
                if self.core_linux_backup != '':
                    self._delete_content(self.core_linux_path)
                    self._delete_content(self.renamed_core_linux_path)
                    self.updater_service.extract_zip(self.core_linux_backup, self.core_linux_path)
                if self.src_backup != '' and os.path.exists(self.genesis_src_path):
                    self._delete_content(self.genesis_src_path)
                    self._delete_content(self.renamed_src_path)
                    self.updater_service.extract_zip(self.src_backup, self.genesis_src_path)
                if self.htdocs_backup != '':
                    self._delete_content(self.htdocs_path)
                    self._delete_content(self.renamed_htdocs_path)
                    self.updater_service.extract_zip(self.htdocs_backup, self.htdocs_path)

            except (Exception,):
                logger.exception("Error while executing rollback")
                if retries == max_retries:
                    raise RollbackError()

                retries += 1
                continue

            logger.info("Rollback finished")
            break

    def _rename_path(self, path, renamed_path):
        # type: (str, str) -> None

        try:
            logger.info("Renaming folder from {} to {}".format(path, renamed_path))

            self.updater_service.rename_folder(path, renamed_path)

            logger.info("Data folder renamed!")
        except (Exception,):
            logger.exception("Error renaming data folder")
            raise ErrorDeletingContent()

    def _download_performed(self, update_id, backoffice_version, file_src_path, file_core_path):
        if update_id == 0:
            return False

        src_new_version = backoffice_version["srcVersionNumber"]
        src_version_hash = backoffice_version["srcVersionHash"]
        file_src_hash_path = os.path.join(self.downloads_path, src_new_version + "-" + src_version_hash)
        src_hash_ok = os.path.exists(file_src_hash_path)

        core_new_version = backoffice_version["coreVersionNumber"]
        core_version_hash = backoffice_version["coreVersionHash"]
        file_core_hash_path = os.path.join(self.downloads_path, core_new_version + "-" + core_version_hash)
        core_hash_ok = os.path.exists(file_core_hash_path)

        has_src = os.path.exists(file_src_path) and src_hash_ok
        has_core = os.path.exists(file_core_path) and core_hash_ok
        if has_src and has_core:
            return True

        self.api_repository.delete_file(file_src_hash_path)
        self.api_repository.delete_file(file_core_hash_path)
        self.api_repository.delete_file(file_src_path)
        self.api_repository.delete_file(file_core_path)
        self.pos_repository.remove_downloaded_update(update_id)

        return False

    def has_binary_update(
        self
    ):
        # type: (...) -> Dict

        result = self.pos_repository.get_last_binary_update_not_applied_controller_id()
        update_id = result.get("update_id", 0)

        if update_id == 0 or not self._can_update_in_current_day():
            return {"update": "NO_PENDING_UPDATE"}
        else:
            if self.force_update_days == 0:
                return {"update": "UPDATE_NOW"}

            downloaded_date = result.get("downloaded_date", None)
            if downloaded_date is not None and (datetime.now() - downloaded_date).days >= self.force_update_days:
                return {"update": "UPDATE_NOW"}

            last_update_name = self.pos_repository.get_last_binary_update_name()
            current_update_name = result.get("update_name", None)
            return {
                "update": "HAS_PENDING_UPDATE",
                "lastVersion": last_update_name,
                "currentVersion": current_update_name
            }

    def _can_update_in_current_day(
        self
    ):
        # type: (...)-> bool

        weekday = datetime.today().weekday()
        return weekday not in self.restrict_days_update

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

        if CommonService().check_is_dev():
            return True

        return os.path.exists(self.python3_path)

    def reapply_last_binary_update(
        self,  # type: BinaryUpdaterInteractor
    ):
        # type: (...) -> None

        try:
            self.pos_repository.invalidate_last_binary_update()
            self.update_binaries(
                data="updateNow",
            )
        except (Exception,):
            logger.exception("Error reapplying last binary")
