import os

import msgbus
from systools import sys_parsefilepath

from application.model.customexception import NoUpdateFound
from typing import Optional
from threading import Thread
from application.interactor import CatalogUpdaterInteractor
from application.manager._UpdaterManager import UpdaterManager
from application.model import UpdateType, UpdateStep, UpdateStatus
from application.model.configuration import Configurations
from application.repository.api import CatalogUpdaterAPIRepository
from application.repository.pos import (
    BinaryUpdaterPOSRepository,
    CatalogUpdaterPOSRepository,
    CommonPOSRepository,
)
from application.service import (
    MediaUpdaterService,
    UpdaterService,
)
from application.util import LoggerUtil

logger = LoggerUtil.get_logger_name(__name__)
MAX_ATTEMPT = 3

GENESIS_PATH = "../../genesis"


class CatalogUpdaterManager(UpdaterManager):
    def __init__(
        self,
        configs,                    # type: Configurations
        interactor,                 # type: CatalogUpdaterInteractor
        api_repository,             # type: CatalogUpdaterAPIRepository
        pos_repository,             # type: CatalogUpdaterPOSRepository
        updater_service,            # type: UpdaterService
        media_updater_service,      # type: MediaUpdaterService,
        binaries_repository,        # type: BinaryUpdaterPOSRepository
        common_pos_repository,      # type: CommonPOSRepository
    ):
        # type: (...) -> None # noqa

        name = UpdateType.catalog.name
        super(CatalogUpdaterManager, self).__init__(name, configs, interactor, pos_repository, logger)
        self.configs = configs
        self.update_type = UpdateType.catalog
        self.local_configs = configs.updaters[self.update_type.name]
        self.active_frequency = int(self.local_configs.active_frequency)
        self.interactor = interactor
        self.api_repository = api_repository
        self.pos_repository = pos_repository
        self.updater_service = updater_service
        self.media_updater_service = media_updater_service
        self.binaries_repository = binaries_repository
        self.common_pos_repository = common_pos_repository

        data_path = sys_parsefilepath("{HVDATADIR}")
        self.data_relative_path = "data"
        self.data_path = os.path.join(data_path, GENESIS_PATH, self.data_relative_path)
        self.genesis_databases_path = os.path.join(data_path, "../../genesis/data/server/databases")

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

        self._verify_necessary_product_info()

        catalog_notify_thread = Thread(target=self._notify_update, name="Catalog Notify")
        catalog_notify_thread.daemon = True
        catalog_notify_thread.start()

        super(CatalogUpdaterManager, self).run()

    def _verify_necessary_product_info(self):
        product_db = os.path.join(self.genesis_databases_path, "product.db")
        if not os.path.exists(product_db):
            return

        table_exists = self.common_pos_repository.table_exists_in_db(
            database_path=product_db,
            table_name="PosSaleTypeNavigation",
        )
        if not table_exists or not self.common_pos_repository.has_pos_sale_type_navigation_populated(product_db):
            self.update(force=True)

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

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

    def update(
        self,               # type: CatalogUpdaterManager
        force=False,        # type: bool
    ):
        # type: (...) -> None

        self.updater_service.download_campaign_pending_version(force=force)
        self.interactor.apply_update(force=force)

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

        while self.running:
            try:
                database_id, update_id = self.pos_repository.get_pending_update_to_notify()
                if update_id:
                    update_id = int(update_id)
                    logger.info("Notifying update {}".format(str(update_id)))
                    self.api_repository.notify_applied_update(database_id, update_id)
                    self.pos_repository.update_step(update_id, UpdateStep.notify)
                    self.pos_repository.update_status(update_id, UpdateStatus.applied)
                    logger.info("Update {} successfully notified!".format(str(update_id)))
                    self._clean_rupture()
                    self.media_updater_service.update_media(force_update=True)
            except Exception as _:
                logger.exception("Error on automatically notify update")
            finally:
                database_id, update_id = self.pos_repository.get_pending_update_to_notify()
                if database_id is not None and update_id is not None:
                    self.sleep(30)
                else:
                    self.sleep(self.active_frequency)

    def _clean_rupture(self):
        # type: ()-> ()

        try:
            logger.info("Start clean rupture")
            attempt = 0
            while attempt < MAX_ATTEMPT:
                message = self.pos_repository.clean_rupture()
                if message.token == msgbus.TK_SYS_ACK:
                    break
                attempt += 1
                logger.info("Clean rupture attempt: {}".format(attempt))
                self.sleep(1)
        except Exception as _:
            logger.exception("Error on clean rupture")

        logger.info("End clean rupture")
