import json
import os
import requests

from typing import Dict
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 ErrorDownloadingBinaries
from application.util import LoggerUtil, ErrorResponseUtil

logger = LoggerUtil.get_logger_name(__name__)


class BinaryUpdaterAPIRepository(object):
    def __init__(self, configs):
        # type: (Configurations) -> None

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

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

        response_backoffice = self.make_request(self._get_backoffice_url())
        if response_backoffice is None:
            raise ErrorDownloadingBinaries(self.error_response)
        return json.loads(response_backoffice.text)

    def download_src(self, src_version_link, file_src_path):
        # type: (str, str) -> None

        logger.info("Downloading src")
        response_src = self.make_request_zip(src_version_link)
        self._save_package(file_src_path, response_src)

    def download_core(self, core_version_link, file_core_path):
        # type: (str, str) -> None

        logger.info("Downloading core")
        response_core = self.make_request_zip(core_version_link)
        self._save_package(file_core_path, response_core)

    @staticmethod
    def save_file(file_path, file_content):
        # type: (str, str) -> None

        with open(file_path, "w") as object:
            object.write(file_content)

    @staticmethod
    def delete_file(file_path):
        # type: (str) -> None

        has_file = os.path.exists(file_path)
        if has_file:
            os.remove(file_path)

    def notify_update(
        self,           # type: BinaryUpdaterAPIRepository
        version_id,     # type: str
    ):
        # type: (str) -> bool

        response = self.make_request(self._get_backoffice_url() + "/" + version_id, "PUT")
        return response is not None

    def make_request(self, url, method="GET"):
        # type: (str, str) -> Response

        headers = self._get_json_headers()
        response = retry(
            logger=logger,
            method_name="backoffice",
            n=3,
            sleep_seconds=5,
            trying_to_do=lambda: requests.request(method, url=url, headers=headers, timeout=60),
            is_success=lambda resp: resp.status_code == 200,
            success=lambda resp: resp,
            failed=lambda resp: self._get_error_response(resp))

        return response

    def make_request_zip(self, url):
        # type: (str) -> Response

        headers = self._get_tar_gz_headers()
        response = retry(
            logger=logger,
            method_name="download_binaries",
            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 == 200,
            success=lambda resp: resp,
            failed=lambda resp: self._get_error_response(resp))

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

        return response

    @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

        package = requests.get(download_url, allow_redirects=True)
        return package

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

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

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

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

    @staticmethod
    def _get_tar_gz_headers():
        # type: () -> Dict[str, str]

        return {"Content-type": "application/x-tar"}

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

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