import json
from logging import Logger

import requests
from application.model import UpdateType
from application.model.configuration import Configurations
from application.model.customexception import \
    ErrorRetrievingPendingUpdate, \
    ErrorDownloadingUpdatePackage, \
    ErrorNotifyingAppliedUpdate
from helper import retry, json_serialize
from requests import Response
from typing import Dict
from application.util import ErrorResponseUtil
from application.util import settings

CATALOG_NOT_FOUND = "$CATALOG_NOT_FOUND"


class CatalogUpdaterAPIRepository(object):

    def __init__(self, logger, configs):
        # type: (Logger, Configurations) -> None

        self.logger = logger
        self.configs = configs
        self.update_type = UpdateType.catalog
        self.local_configs = configs.updaters[self.update_type.name]
        self.response_util = ErrorResponseUtil()
        self.error_response = None

    def retrieve_pending_update(self):
        # type: () -> str

        url = self._get_download_url()
        headers = self._get_app_json_headers()

        response = retry(
                logger=self.logger,
                method_name="retrieve_pending_update",
                n=3,
                sleep_seconds=3,
                trying_to_do=lambda: requests.request("GET", url=url, headers=headers, timeout=10, verify=False),
                is_success=lambda resp: resp.status_code == 200,
                success=lambda resp: resp,
                failed=lambda resp: self._get_error_response(resp))

        if response is None:
            settings.set_update_control("catalog", self.error_response)
            raise ErrorRetrievingPendingUpdate(self.error_response)

        return json.loads(response.content)

    def get_menu_databases(self, menu_link):
        # type: (str) -> Response

        self._init_message_update()

        package = requests.get(menu_link, allow_redirects=True, verify=False)
        self._check_has_file(package)
        self.logger.info("Package successfully downloaded!")

        return package

    def notify_applied_update(self, database_id, update_id):
        # type: (str, int) -> None

        url = self._get_updated_to_url(update_id)
        headers = self._get_app_json_headers()
        data = {"pos_transaction_id": "{}_{}".format(str(database_id), str(update_id))}

        response = retry(
                logger=self.logger,
                method_name="notify_applied_update",
                n=3,
                sleep_seconds=5,
                trying_to_do=lambda: requests.request("POST", url=url, headers=headers, json=data, timeout=120),
                is_success=lambda resp: resp.status_code == 204,
                success=lambda resp: resp,
                failed=lambda resp: self._get_error_response(resp))

        if response is None:
            raise ErrorNotifyingAppliedUpdate(self.error_response)

    def _get_app_json_headers(self):
        # type: () -> Dict[str, str]

        return {
            "X-Api-Key": self.configs.api_key,
            "Content-type": "application/json",
            "Accept": "text/plain"
        }

    def _get_app_zip_headers(self):
        # type: () -> Dict[str, str]

        return {"X-Api-Key": self.configs.api_key}

    def _get_latest_version_url(self):
        # type: () -> str

        end_point_url = self.local_configs.endpoints.get_latest_version.format(storeId=self.configs.store_id)
        return self.configs.base_url + end_point_url

    def _get_download_url(self):
        # type: () -> str

        end_point_url = self.local_configs.endpoints.download.format(storeId=self.configs.store_id)
        return self.configs.base_url + end_point_url

    def _get_updated_to_url(self, updated_id):
        # type: (int) -> str

        end_point_url = self.local_configs.endpoints.update_to.format(
            storeId=self.configs.store_id,
            menu_id=str(updated_id)
        )
        return self.configs.base_url + end_point_url

    def _get_error_response(self, resp):
        # type: (Response) -> None

        self.logger.error("Error downloading catalog: {}".format(json_serialize(resp)))
        self.error_response = self.response_util.get_error_response(resp, CATALOG_NOT_FOUND)

    @staticmethod
    def _check_has_file(package):
        # type: (Response) -> None

        if package is not None and package.status_code == 403:
            settings.set_update_control("catalog", CATALOG_NOT_FOUND)
            raise ErrorDownloadingUpdatePackage(CATALOG_NOT_FOUND)

    @staticmethod
    def _init_message_update():
        settings.set_update_control("catalog", None)
