import json
import os

from copy import deepcopy
from datetime import datetime
from threading import Lock
from os.path import isfile, join

from systools import sys_parsefilepath

import pytz
from application.model import UpdateType, MediaUpdateData, UpdateMediaJson
from application.model.configuration import Configurations
from application.model.customexception import ErrorRetrievingPendingUpdate, ErrorApplyingUpdate, \
    HasNoProductsImagesToDownload, NoUpdateFound, NotExpectedException, DisabledFunctionality, \
    ErrorGettingTotemConfig, NotAllImagesDownload
from application.repository.api import MediaUpdaterAPIRepository
from application.repository.pos import MediaUpdaterPOSRepository
from application.service import UpdaterService
from iso8601 import iso8601
from requests import Response
from typing import (
    Any,
    Dict,
    List,
    Optional,
    Union,
)
from application.util import LoggerUtil

logger = LoggerUtil.get_logger_name(__name__)

allowed_video_extensions = ['mp4', 'webm', 'ogg']


class MediaUpdaterService(object):
    def __init__(
        self,
        configs,            # type: Configurations
        working_on_update,  # type: Lock
        api_repository,     # type: MediaUpdaterAPIRepository
        pos_repository,     # type: MediaUpdaterPOSRepository
        updater_service,    # type: UpdaterService
        enabled,            # type: bool
    ):
        # type: (...) -> None

        self.logger = logger
        self.configs = configs
        self.update_type = UpdateType.media
        self.local_configs = configs.updaters[self.update_type.name]
        self.working_on_update = working_on_update
        self.api_repository = api_repository
        self.pos_repository = pos_repository
        self.updater_service = updater_service
        self.enabled = enabled

        self.images_path = self.local_configs.images_directory
        if not os.path.exists(self.images_path):
            os.makedirs(self.images_path)

        data_path = sys_parsefilepath("{HVDATADIR}")

        self.control_json_path = os.path.join(self.images_path, "imagesHashes.json")
        self.control_json_totem_path = os.path.join(self.images_path, "../static/totemConfig.json")

        self.totem_config_json_file_name = "totemConfig.json"
        self.totem_config_original_path = "../src/internalorder/static"
        self.totem_config_destination_path = os.path.join(data_path, "bundles", "internalorder")

    def update_media(
        self,
        force_update,   # type: bool
    ):
        # type: (...) -> None

        if not self.enabled:
            raise DisabledFunctionality

        try:
            self.logger.info("Trying to update media...")
            some_media_downloaded = False
            if force_update or self.pos_repository.has_pending_update_to_apply(UpdateType.banners.value):
                totem_configs = self._get_totem_configs()
                some_media_downloaded |= self._download_totem_media(totem_configs)
                self.update_totem_config_json(totem_configs)
            if force_update or self.pos_repository.has_pending_update_to_apply():
                some_media_downloaded |= self._download_media()

            self.logger.info("Media update fully concluded!")
            if not some_media_downloaded:
                raise NoUpdateFound()
        except NoUpdateFound as _:
            self.logger.info("No pending updates found")
            raise
        except HasNoProductsImagesToDownload as _:
            self.logger.info("Has not products to download images")
            raise
        except ErrorRetrievingPendingUpdate as _:
            self.logger.exception("Error retrieving pending update")
            raise
        except ErrorApplyingUpdate as _:
            self.logger.error("Error doing update")
            raise
        except (NotExpectedException, Exception) as _:
            self.logger.exception("Not expected exception on media update")
            raise

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

        self.logger.info("Trying to update media...")

        json_images_hash = self._get_images_hashes_from_json()
        catalog_db = self.pos_repository.get_images_hashes_from_database()
        navigation_db = self.pos_repository.get_images_hashes_from_navigation()
        try:
            catalog = json_images_hash.catalog
            navigation = json_images_hash.navigation
            images_to_download = self._filter_images_to_download(catalog_db, catalog)
            images_to_download.update(self._filter_images_to_download(navigation_db, navigation))

            if not images_to_download:
                self.pos_repository.insert_new_media_update()
                return False

            images_to_download = self._format_images_to_update_data(images_to_download, navigation_db)
            self._get_images_permissions(images_to_download)
            self._download_and_store_images(images_to_download)
            json_images_hash.catalog = self._update_images_hashes_json(catalog_db, catalog, images_to_download)
            json_images_hash.navigation = self._update_images_hashes_json(navigation_db, navigation, images_to_download)
            not_downloaded_images = self._validate_all_images_was_downloaded(images_to_download)
            self._save_file(json_images_hash)
            self.pos_repository.insert_new_media_update()

            if len(not_downloaded_images) > 0:
                self.logger.info("Not all images was downloaded: {}".format(",".join(not_downloaded_images)))
                raise NotAllImagesDownload()
        finally:
            self._remove_old_images_catalog_and_navigation(json_images_hash)

        return True

    def update_totem_config_json(
        self,
        totem_configs,  # type: Dict
    ):
        # type: (...) -> None

        if not os.path.exists(self.totem_config_destination_path):
            return

        destination_file = self._create_totem_config_file_if_not_exists()
        self._update_totem_config_json(destination_file, totem_configs)

    @staticmethod
    def _get_custom_images_totem_configs(
        totem_configs,    # type: Dict
    ):
        # type: (...) -> List[dict]

        custom_images = []
        if "customImages" in totem_configs:
            for custom_image in totem_configs.get("customImages"):
                key = custom_image.keys()[0]
                img_path = custom_image.get(key).get("imagePath")
                if "sui" not in img_path:
                    custom_image[key]["imagePath"] = "sui/images/{}".format(img_path)
                custom_images.append(custom_image)
        return custom_images

    @staticmethod
    def _parse_media_to_totem_config(
        totem_configs,                         # type: Dict[str, Dict[str, str]]
        key,                                   # type: str
        image_path_format="/sui/images/{}",    # type: Optional[str]
    ):
        # type: (...) -> Dict[str, str]

        media_configs = {}
        if totem_configs.get(key):
            for media in totem_configs.get(key, []):
                media_id = media.get("imageId", "")
                media_name = media.get("name", "")
                media_configs[media_id] = image_path_format.format(media_name)

        return media_configs

    def _update_totem_config_json(
        self,
        destination_file,   # type: str
        totem_configs,      # type: Dict
    ):
        # type: (...) -> None

        image_path_format = "/sui/images/{}"

        with open(destination_file, "r") as totem_config_file:
            new_totem_configs = json.load(totem_config_file)

            if totem_configs.get("style"):
                new_totem_configs["style"] = totem_configs.get("style")

            if totem_configs.get("banner"):
                new_totem_configs["banner"] = totem_configs.get("banner")

                banners = totem_configs.get("banner")
                for banner in banners["images"]:
                    banner["name"] = image_path_format.format(banner["name"])

            if totem_configs.get("payments"):
                new_totem_configs["payments"] = totem_configs.get("payments")

                payments = totem_configs.get("payments")
                for payment_id in payments.keys():
                    payments[payment_id]["name"] = image_path_format.format(payments[payment_id]["name"])

            new_totem_configs["images"].update(
                self._parse_media_to_totem_config(
                    totem_configs=totem_configs,
                    key="images",
                )
            )

            new_totem_configs['customImages'] = self._get_custom_images_totem_configs(
                totem_configs=totem_configs,
            )

            new_totem_configs["videos"] = {}
            if totem_configs.get('videos', []):
                new_totem_configs["videos"].update(
                    self._parse_media_to_totem_config(
                        totem_configs=totem_configs,
                        key="videos",
                    )
                )

        with open(destination_file, "w") as totem_config_file:
            json.dump(new_totem_configs, totem_config_file)

    def _create_totem_config_file_if_not_exists(
        self
    ):
        # type: (...) -> str

        destination_file = os.path.join(
            self.totem_config_destination_path,
            self.totem_config_json_file_name
        )
        valid_totem_config = False

        try:
            if os.path.exists(destination_file):
                with open(destination_file, "r") as totem_config_file:
                    json.load(totem_config_file)
                    valid_totem_config = True
        except ValueError as _:
            pass

        if not valid_totem_config:
            original_file = os.path.join(
                self.totem_config_original_path,
                self.totem_config_json_file_name
            )
            self.updater_service.copy_file(
                original_file_path=original_file,
                destination_file_path=destination_file,
            )

        return destination_file

    def _remove_old_images_catalog_and_navigation(self, images_hash):
        # type: (UpdateMediaJson) -> None

        try:
            prt_nav_images = [f for f in os.listdir(self.images_path) if self._is_product_or_nav(f)]
            prt_images = ["PRT" + product_code.zfill(8) for product_code in images_hash.catalog.keys()]
            nav_images = ["NAV" + product_code.zfill(8) for product_code in images_hash.navigation.keys()]

            for file_name in prt_nav_images:
                if file_name[:11] in prt_images or file_name[:11] in nav_images:
                    continue

                os.remove(os.path.join(self.images_path, file_name))

        except Exception as _:
            self.logger.exception("Error remove old images")

    def _is_product_or_nav(self, file_name):
        # type: (str) -> bool

        is_product = file_name.startswith("PRT")
        is_navigation = file_name.startswith("NAV")
        return isfile(join(self.images_path, file_name)) and (is_product or is_navigation)

    @staticmethod
    def _validate_all_images_was_downloaded(images_to_download):
        images_not_downloaded = []
        for image in images_to_download:
            if not image.downloaded:
                images_not_downloaded.append(image.product_code)

        return images_not_downloaded

    def _download_totem_media(
        self,
        totem_configs,  # type: Dict
    ):
        # type: (...) -> bool

        some_totem_media_downloaded = False
        try:
            images_hashes = self._get_images_hashes_from_json()
            some_totem_media_downloaded |= self._download_banner_images(
                totem_configs=totem_configs,
                images_hashes=images_hashes,
            )
            some_totem_media_downloaded |= self._download_totem_images(
                totem_configs=totem_configs,
                stored_images_hash=images_hashes,
            )
            self.pos_repository.insert_new_media_update(UpdateType.banners.value)
        except ErrorGettingTotemConfig as _:
            self.logger.warning("Error downloading totem media")
        except Exception as _:
            self.logger.exception("Error downloading totem media")

        return some_totem_media_downloaded

    def _download_banner_images(
        self,
        totem_configs,  # type: Dict
        images_hashes,  # type: UpdateMediaJson
    ):
        # type: (...) -> bool

        self.logger.info("Trying to update banner media...")

        some_banner_media_downloaded = False
        stored_banners = images_hashes.banners_totem.copy()
        banners_json = totem_configs.get("banner", "{}")
        images_to_download = self._get_medias_to_download(banners_json, stored_banners.values())
        self._update_from_remote_medias(
            totem_configs=totem_configs,
            stored_medias_hash=images_hashes,
            medias_to_download=images_to_download,
            is_banner=True,
        )

        if len(images_to_download) > 0:
            images_to_download = self._download_and_save(images_to_download)

            images_hash = self._get_medias_hashes_from_stored_json()
            banners_totem = self._remove_and_update_config(
                totem_configs=totem_configs,
                medias_to_download=images_to_download,
                medias_hash=images_hash,
                medias_hashes_from_json=stored_banners,
                is_banner=True,
            )
            images_hashes.banners_totem = banners_totem.get('images')
            self._save_file(images_hashes)
            some_banner_media_downloaded = True

        return some_banner_media_downloaded

    @staticmethod
    def _get_medias_to_delete(
        totem_configs,          # type: dict
        stored_image_hashes,    # type: list
        is_banner=False,        # type: Optional[bool]

    ):
        # type: (...) -> list

        totem_images = totem_configs if not is_banner else totem_configs.get('banner', {}) or {}

        medias_to_download = [img_data.get('hashImage') for img_data in totem_images.get('images', [])]
        medias_to_download += [video_data.get('hashImage') for video_data in totem_images.get('videos', [])]

        images_to_delete = []
        for img_hash in stored_image_hashes:
            if img_hash not in medias_to_download:
                images_to_delete.append(img_hash)

        return images_to_delete

    def _delete_medias_files(
        self,
        stored_hashes_json,     # type: dict
        hashes_to_delete,       # type: list

    ):
        # type: (...) -> None

        medias_data = stored_hashes_json.get('images') or []
        medias_data += stored_hashes_json.get('banner', {}).get('images') or []
        medias_data += (stored_hashes_json.get('videos') or [])
        for img_hash in hashes_to_delete:
            for img_data in medias_data:
                if img_hash == img_data.get('hashImage'):
                    file_path = os.path.join(
                        self.images_path,
                        img_data.get('path').replace('/sui/images\\', ''),
                    )
                    self.updater_service.delete_file(path=file_path)

    def _update_from_remote_medias(
        self,
        totem_configs,          # type: dict
        stored_medias_hash,     # type: UpdateMediaJson
        medias_to_download,     # type: List[MediaUpdateData]
        is_banner=False,        # type: Optional[bool]
    ):
        # type: (...) -> None

        if is_banner:
            stored_hashes = stored_medias_hash.banners_totem.copy()
        else:
            stored_hashes = stored_medias_hash.images_totem.copy()
            stored_hashes.update(stored_medias_hash.videos_totem)

        medias_to_delete = self._get_medias_to_delete(
            totem_configs=totem_configs,
            stored_image_hashes=stored_hashes.values(),
            is_banner=is_banner,
        )

        if medias_to_delete:
            medias_hashes_stored_json = self._get_medias_hashes_from_stored_json()
            self._delete_medias_files(
                stored_hashes_json=medias_hashes_stored_json,
                hashes_to_delete=medias_to_delete,
            )

            medias_hashes = self._remove_and_update_config(
                totem_configs=totem_configs,
                medias_to_download=medias_to_download,
                medias_hash=medias_hashes_stored_json,
                medias_hashes_from_json=stored_hashes,
                is_banner=is_banner,
                delete_hashes=medias_to_delete,
            )

            if is_banner:
                stored_medias_hash.banners_totem = medias_hashes.get('images')
            else:
                stored_medias_hash.images_totem = medias_hashes.get('images')
                stored_medias_hash.videos_totem = medias_hashes.get('videos')

            self._save_file(update_data=stored_medias_hash)

    def _download_totem_images(self, totem_configs, stored_images_hash):
        # type: (dict, UpdateMediaJson) -> bool

        self.logger.info("Trying to update totem_stored_hashs totem media...")

        some_totem_image_downloaded = False
        totem_stored_hashes = stored_images_hash.images_totem.copy()
        totem_stored_hashes.update(stored_images_hash.videos_totem)
        medias_to_download = self._get_medias_to_download(
            totem_configs=totem_configs,
            downloaded_medias=totem_stored_hashes.values(),
        )

        self._update_from_remote_medias(
            totem_configs=totem_configs,
            stored_medias_hash=stored_images_hash,
            medias_to_download=medias_to_download,
        )

        if len(medias_to_download) > 0:
            medias_to_download = self._download_and_save(
                images_to_download=medias_to_download,
                is_totem_image=True,
            )

            hashes = self._get_medias_hashes_from_stored_json()
            medias_totem = self._remove_and_update_config(
                totem_configs=totem_configs,
                medias_to_download=medias_to_download,
                medias_hash=hashes,
                medias_hashes_from_json=totem_stored_hashes,
                is_banner=False,
            )
            stored_images_hash.images_totem = medias_totem.get('images')
            stored_images_hash.videos_totem = medias_totem.get('videos')
            self._save_file(update_data=stored_images_hash)
            some_totem_image_downloaded = True

        return some_totem_image_downloaded

    def _download_and_save(
        self,
        images_to_download,     # type: (List[MediaUpdateData]),
        is_totem_image=False    # type: bool
    ):
        # type: (...) -> List[MediaUpdateData]

        self._get_images_permissions(images_to_download)
        return self._download_and_store_images(
            images_to_download=images_to_download,
            is_totem_image=is_totem_image,
        )

    def _generate_updated_hashes_dict(
        self,
        totem_configs,          # type: dict
        medias_to_download,     # type: List[MediaUpdateData]
        delete_hashes,          # type: list
        is_banner=False,        # type: bool
    ):
        # type: (...) -> dict

        hashes_from_json = self._get_images_hashes_from_json()
        images = hashes_from_json.images_totem if not is_banner else hashes_from_json.banners_totem
        videos = hashes_from_json.videos_totem
        updated_hashes = {
            "images": {image_hash: image_hash for image_hash in images if image_hash not in delete_hashes},
            "videos": {video_hash: video_hash for video_hash in videos if video_hash not in delete_hashes},
        }

        medias_data = totem_configs if not is_banner else totem_configs.get('banner', {}) or {}
        medias_data = medias_data.get('images', []) + medias_data.get('videos', [])

        for media_data in medias_data:
            for media_to_download in medias_to_download:
                same_hash = media_data.get('hashImage') == media_to_download.image_hash
                same_name = media_data.get('name') == media_to_download.image_name
                if same_hash and same_name and media_to_download.successfully_downloaded:
                    key = 'videos' if media_data.get('name').split('.')[1] in allowed_video_extensions else 'images'
                    updated_hashes[key][media_data.get('hashImage')] = media_data.get('hashImage')

        return updated_hashes

    def _remove_and_update_config(
        self,
        totem_configs,              # type: Dict
        medias_to_download,         # type: List[MediaUpdateData]
        medias_hash,                # type: Dict
        medias_hashes_from_json,    # type: Dict
        is_banner,                  # type: bool
        delete_hashes=None,         # type: Optional[List]
    ):
        # type: (...) -> Dict

        if delete_hashes is None:
            delete_hashes = []
        old_data_hashes = deepcopy(medias_hash)
        self._update_json_totem_config(totem_configs, medias_to_download, medias_hash, is_banner)
        downloaded = [x.image_hash for x in medias_to_download if x.downloaded]
        update_images_hashes = self._update_images_hashes_json(downloaded, medias_hashes_from_json, medias_to_download)

        if is_banner:
            self._remove_old_banners_from_path(totem_configs, old_data_hashes, update_images_hashes)

        return self._generate_updated_hashes_dict(
            totem_configs=totem_configs,
            medias_to_download=medias_to_download,
            delete_hashes=delete_hashes,
            is_banner=is_banner,
        )

    def _get_medias_to_download(
        self,                   # type: MediaUpdaterService
        totem_configs,          # type: Dict
        downloaded_medias,      # type: Any[Union[List, Dict]]
    ):
        # type: (...) -> List[MediaUpdateData]

        if totem_configs is None:
            return []

        medias = totem_configs.get("images", []) + totem_configs.get("videos", [])
        payment_images = totem_configs.get("payments", {})
        if payment_images is None:
            return []

        for payment_image in payment_images.values():
            medias.append(payment_image)

        new_medias_to_download = []
        if len(medias) > 0:
            new_medias_to_download = self._get_banner_media_update(medias, downloaded_medias)

        return new_medias_to_download

    @staticmethod
    def _get_banner_media_update(list_data, downloaded_images):
        # type: (dict, Any[Union[List, Dict]]) -> List

        formatted_images_to_download = []
        for data in list_data:
            if data.get("hashImage") not in downloaded_images:
                update_data = MediaUpdateData()
                update_data.product_code = data.get("productId", None)
                update_data.image_name = data.get("name", "")
                update_data.image_hash = str(data.get("hashImage", None))
                update_data.product_type = "TT"
                formatted_images_to_download.append(update_data)

        return formatted_images_to_download

    def _remove_old_banners_from_path(self, images_totem, json_images_client, update_images_hashes):
        # type: (Dict, Dict, Dict) -> None

        images_client = self._get_medias_from_json(
            json_medias_client=json_images_client,
            key="images",
            is_banner=True
        )
        images_server = self._get_medias_from_json(
            json_medias_client=images_totem,
            key="images",
            is_banner=True
        )
        for hash_image in images_client.keys():
            if hash_image not in images_server.keys():
                image = images_client[hash_image]
                image_name = image.get("name", "")
                image_path = self._get_image_path(image_name)
                if os.path.isfile(image_path):
                    os.remove(image_path)

                continue

            if hash_image not in update_images_hashes.keys():
                update_images_hashes[hash_image] = hash_image

    def _update_images_hashes_json(self, images_hashes_from_database, images_hashes_from_json, images_to_update):
        # type: (Dict, Dict, List[MediaUpdateData]) -> Dict

        self.logger.info("Updating [imagesHashes.json]...")

        for image in images_to_update:
            if image.downloaded:
                key_code = image.product_code if image.product_type != "TT" else image.image_hash
                images_hashes_from_json[key_code] = image.image_hash

        for key_code in images_hashes_from_json.keys():
            if key_code not in images_hashes_from_database:
                del images_hashes_from_json[key_code]

        return self._delete_images_hashes_json(images_hashes_from_database, images_hashes_from_json)

    @staticmethod
    def _delete_images_hashes_json(images_hashes_from_database, images_hashes_from_json):
        # type: (Dict, Dict) -> Dict

        for key_code in images_hashes_from_json.keys():
            if key_code not in images_hashes_from_database:
                del images_hashes_from_json[key_code]

        return images_hashes_from_json

    def _update_json_totem_config(self, medias_from_server, downloaded_medias, json_medias_client, is_banner=True):
        # type: (dict, List[MediaUpdateData], dict, bool) -> None

        downloaded = [x for x in downloaded_medias if x.downloaded]
        images = self._get_updated_client_media(
            key="images",
            downloaded=downloaded,
            json_medias_client=json_medias_client,
            medias_from_server=medias_from_server,
            is_banner=is_banner,
        )

        videos = self._get_updated_client_media(
            key="videos",
            downloaded=downloaded,
            json_medias_client=json_medias_client,
            medias_from_server=medias_from_server,
            is_banner=is_banner,
        )

        if is_banner:
            if not json_medias_client.get("banner"):
                json_medias_client["banner"] = {}

            json_medias_client["banner"]["images"] = images
        else:
            json_medias_client["images"] = images
            json_medias_client["videos"] = videos

        change_time = 30
        if medias_from_server is not None:
            banner = medias_from_server.get("banner", None)
            change_time = banner.get("changeTime", 30) if banner is not None else change_time

        json_medias_client["changeTime"] = change_time
        with open(self.control_json_totem_path, "w") as json_file:
            json.dump(json_medias_client, json_file)

    def _get_updated_client_media(
        self,
        key,                   # type: str
        downloaded,            # type: List[MediaUpdateData]
        json_medias_client,    # type: dict
        medias_from_server,    # type: dict
        is_banner,             # type: bool

    ):
        # type: (...) -> List[Dict]

        media_client = self._get_medias_from_json(
            json_medias_client=json_medias_client,
            key=key,
            is_banner=is_banner,
        )
        media_server = self._get_medias_from_json(
            json_medias_client=medias_from_server,
            key=key,
            is_banner=is_banner,
        )

        medias = []
        for hash_media in media_server.keys():
            if hash_media in media_client.keys():
                medias.append(media_client[hash_media])
                continue
            data = self._get_downloaded_medias(
                hash_media=hash_media,
                downloaded=downloaded,
            )
            if data is not None:
                medias.append(data)

        return medias

    def _get_downloaded_medias(self, hash_media, downloaded):
        # type: (str, List[MediaUpdateData]) -> Dict

        for image in downloaded:
            if image.image_hash == hash_media:
                name = image.image_name
                image_path = self.images_path[self.images_path.index("/sui"):]
                path = os.path.join(image_path, image.image_name)
                return {
                    'name': name,
                    'hashImage': image.image_hash,
                    'path': path,
                    'productId': image.product_code
                }

    @staticmethod
    def _get_medias_from_json(
        json_medias_client,     # type: Dict
        key,                    # type: str
        is_banner,              # type: bool
    ):
        # type: (...) -> dict

        medias_client = json_medias_client
        if is_banner:
            medias_client = medias_client.get("banner", {})
        medias = medias_client.get(key, []) if medias_client is not None else []

        medias_downloaded = dict()
        for media in medias:
            medias_downloaded[str(media.get('hashImage'))] = media

        return medias_downloaded

    def _format_images_to_update_data(self, images_to_download, database_navigation_hash):
        # type: (dict, dict) -> List[MediaUpdateData]

        formatted_images_to_download = list()
        for product_code in images_to_download:
            update_data = MediaUpdateData()
            update_data.product_code = product_code
            update_data.image_hash = images_to_download[product_code]
            update_data.product_type = "NAV" if product_code in database_navigation_hash else "PRT"

            formatted_images_to_download.append(update_data)

        products_to_download = [x.product_code for x in formatted_images_to_download]
        self.logger.info("Trying to obtain images for products: {}".format(products_to_download))

        return formatted_images_to_download

    def _get_images_hashes_from_json(self):
        # type: () -> UpdateMediaJson

        images_hashes_from_json = None
        try:
            images_hashes_from_json = UpdateMediaJson()
            if os.path.isfile(self.control_json_path):
                with open(self.control_json_path) as json_file:
                    json_load = json.load(json_file)
                    if json_load is not None:
                        images_hashes_from_json.catalog = json_load["catalog"]
                        images_hashes_from_json.navigation = json_load["navigation"]
                        images_hashes_from_json.banners_totem = json_load["banners_totem"]
                        images_hashes_from_json.images_totem = json_load["images_totem"]
                        images_hashes_from_json.videos_totem = json_load.get("videos_totem", {})

            return images_hashes_from_json
        except Exception as _:
            os.remove(self.control_json_path)
            return images_hashes_from_json

    def _get_medias_hashes_from_stored_json(self):
        # type: () -> Dict

        try:
            if os.path.isfile(self.control_json_totem_path):
                with open(self.control_json_totem_path) as json_file:
                    return json.load(json_file)

            return {}
        except Exception as _:
            return {}

    @staticmethod
    def _filter_images_to_download(images_hashes_from_database, images_hashes_from_json):
        # type: (Dict, Dict) -> Dict

        product_that_we_need_to_download = {}
        images = images_hashes_from_json
        for product_code in images_hashes_from_database:
            database_hash = images_hashes_from_database[product_code]
            if images is None or product_code not in images or database_hash != images[product_code]:
                product_that_we_need_to_download[product_code] = database_hash

        return product_that_we_need_to_download

    def _get_images_permissions(self, images_to_download):
        # type: (List[MediaUpdateData]) -> None

        try:
            hashes_to_get_permission = []
            for image_to_download in images_to_download:
                hashes_to_get_permission.append(image_to_download.image_hash)

            images_permissions = self.api_repository.get_images_permissions(hashes_to_get_permission)

            for image_permission in images_permissions:
                obtained_image_hash = image_permission.get("imageHash")
                for image_to_download in images_to_download:
                    if image_to_download.image_hash == obtained_image_hash:
                        image_to_download.link_to_download = image_permission.get("link")
                        image_to_download.expiration_date = iso8601.parse_date(image_permission.get("expirationDate"))

        except Exception as ex:
            raise ErrorRetrievingPendingUpdate(ex)

    def _get_totem_configs(self):
        # type: () -> Dict

        try:
            return self.api_repository.get_totem_config()
        except Exception as ex:
            raise ErrorGettingTotemConfig(ex)

    def _download_and_store_images(
        self,
        images_to_download,     # type: List[MediaUpdateData]
        is_totem_image=False    # type: bool
    ):
        # type: (...) -> List[MediaUpdateData]

        backup_file_name = None
        for image_to_download in images_to_download:
            try:
                now = self._get_utc_now()
                image_type = " product" if image_to_download.product_code else ""
                name = image_to_download.image_name
                if image_to_download.product_code:
                    name = image_to_download.product_code

                if image_to_download.expiration_date and now > image_to_download.expiration_date:
                    self.logger.error("Expiration date reached for {}: {}".format(image_type, name))
                    continue

                if not image_to_download.link_to_download:
                    self.logger.error("Image has no link to download: {}".format(name))
                    continue

                package = self.api_repository.download_image(image_to_download.link_to_download)
                image_to_download.successfully_downloaded = package.ok
                if not package.ok:
                    self.logger.error("Could not download image for {}: {}".format(image_type, name))
                    continue

                image = image_to_download
                if image.product_type == "TT":
                    product_image_name = image.image_name
                else:
                    product_image_name = self._format_product_image_name(image.product_code, image.product_type)
                product_image_path = self._get_image_path(product_image_name)

                if is_totem_image:
                    backup_file_name = self._backup_file(product_image_path)

                self._store_image(product_image_path, package)
                self._remove_backup_file(backup_file_name)

                image_to_download.downloaded = True

                self.logger.info("Image downloaded and stored for product: {}".format(image_to_download.product_code))
            except Exception as _:
                self._restore_backup_file(backup_file_name)
                self.logger.info("Image downloaded and stored for product: {}".format(image_to_download.product_code))
            finally:
                backup_file_name = None

        return images_to_download

    def _get_image_path(self, prt_image_name):
        # type: (str) -> str

        return os.path.join(self.images_path, prt_image_name)

    @staticmethod
    def _format_product_image_name(product_code, product_type):
        # type: (str, bool) -> str

        return "{}{}.png".format(product_type, product_code.zfill(8))

    @staticmethod
    def _store_image(image_path, package):
        # type: (str, Response) -> None

        with open(image_path, "wb") as image_file:
            for chunk in package.iter_content(chunk_size=128):
                image_file.write(chunk)

    def _save_file(self, update_data):
        with open(self.control_json_path, "w") as json_file:
            json.dump(vars(update_data), json_file)

    @staticmethod
    def _backup_file(
            image_path  # type: str
    ):
        # type: (...) -> str

        image_part = image_path.split('.')
        image_part = "{}_old.{}".format(image_part[0], image_part[1])
        if os.path.exists(image_path):
            os.rename(image_path, image_part)

        return image_part

    @staticmethod
    def _restore_backup_file(
            image_path  # type: str
    ):
        # type: (...) -> None

        if image_path:
            os.rename(image_path, image_path.replace("_old", ""))

    @staticmethod
    def _remove_backup_file(
            image_path  # type: str
    ):
        # type: (...) -> None

        if image_path is not None and os.path.isfile(image_path):
            os.remove(image_path)

    @staticmethod
    def _get_utc_now():
        # type: () -> datetime

        return pytz.UTC.localize(datetime.utcnow())
