import requests
from typing import Dict

from logging import Logger
from requests import Response

from helper import retry, json_serialize

from application.model import UpdateType
from application.model.configuration import Configurations
from application.model.customexception import (
    ErrorDownloadingLoaders,
    InvalidBOHConfigurations,
)
from application.util import ErrorResponseUtil, settings


class LoaderUpdaterAPIRepository(object):

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

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

    def send_result_message(
        self,               # type: LoaderUpdaterAPIRepository
        body,               # type: Dict[str,str]
    ):
        # type: (...) -> Response

        url = self._get_notify_endpoint_url(
            endpoint=self.local_configs.endpoints.update_notify,
        )
        return retry(
            logger=self.logger,
            method_name="send_result_message",
            n=3,
            sleep_seconds=5,
            trying_to_do=lambda: requests.request(
                method="POST",
                url=url,
                headers=self._get_notify_headers(),
                json=body,
                timeout=60,
            ),
            is_success=lambda resp: resp.status_code == 200,
            success=lambda resp: resp,
            failed=lambda resp: self._get_error_response(resp=resp)
        )

    def download_loaders(
        self,               # type: LoaderUpdaterAPIRepository
        file_path,          # type: str
        last_versions,      # type: Dict[str,str]
    ):
        # type: (...) -> None

        url = self._get_download_endpoint_url(
            last_versions=last_versions,
            endpoint=self.local_configs.endpoints.download_loaders,
        )
        headers = self._get_zip_headers()
        response = retry(
            logger=self.logger,
            method_name="download_loaders",
            n=3,
            sleep_seconds=5,
            trying_to_do=lambda: requests.request("GET", url=url, headers=headers, timeout=60),
            is_success=lambda resp: resp.status_code == 302 or resp.status_code == 412,
            success=lambda resp: resp,
            failed=lambda resp: self._get_error_response(resp),
        )

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

        if response.status_code == 412:
            raise InvalidBOHConfigurations(ErrorResponseUtil.get_error_response(response))

        package = self._download_package(response.content.strip('\"'))
        self._check_has_file(package)
        self._save_package(file_path, package)

    @staticmethod
    def _save_package(file_path, package):
        # type: (str, Response) -> None

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

    @staticmethod
    def _download_package(download_url):
        # type: (str) -> Response

        return requests.get(download_url, allow_redirects=True)

    def _get_download_endpoint_url(
        self,               # type: LoaderUpdaterAPIRepository
        last_versions,      # type: Dict[str, str]
        endpoint,           # type: str
    ):
        # type: (...) -> str

        end_point_url = endpoint.format(storeId=self.configs.store_id)
        src_version = last_versions.get(UpdateType.binaries.value).split("|")[1].replace("SRC:", "")
        params_url = "?pos_version={}".format(src_version)
        return self.configs.base_url + end_point_url + params_url

    def _get_notify_endpoint_url(
        self,               # type: LoaderUpdaterAPIRepository
        endpoint,           # type: str
    ):
        # type: (...) -> str

        return self.configs.base_url + endpoint

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

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

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

        return {"X-Api-Key": self.configs.api_key,
                "Content-type": "application/zip"}

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

        self.logger.error("Error downloading loaders: {}".format(json_serialize(resp)))
        self.error_response = self.response_util.get_error_response(resp, "$STORE_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("loaders", "$LOADERS_NOT_FOUND")
            raise ErrorDownloadingLoaders("$LOADERS_NOT_FOUND")
