"""
Common Copr code for dealing with HTTP requests
"""

import logging
import json
import time
import requests

from copr_common import __version__


class SafeRequest:
    """
    Build a HTTP request and _safely_ send it.

    If the server cannot be reached, the request is repeated either indefinitely
    or until a number of failed attempts is reached.
    """

    # Prolong the sleep time before asking frontend again
    SLEEP_INCREMENT_TIME = 5

    def __init__(self, auth=None, cert=None, log=None,
                 try_indefinitely=False, timeout=30, attempts=None,
                 user_agent=None):
        # pylint: disable=too-many-positional-arguments
        self.auth = auth
        self.cert = cert
        self.log = log
        if log is None:
            self.log = logging.getLogger("noop_logger")
            if not self.log.handlers:
                self.log.addHandler(logging.NullHandler())

        self.timeout = timeout

        if attempts and try_indefinitely:
            raise ValueError("Can't set both attempts and try_indefinitely")
        if not attempts and not try_indefinitely:
            attempts = 5

        self.attempts = attempts
        self.try_indefinitely = try_indefinitely

        # Use package name and version for the user agent
        self.package_name = 'copr-common'
        self.user_agent = user_agent or "copr-common/{version}".format(
            version=__version__,
        )

    def get(self, url, **kwargs):
        """
        Issue relentless GET request to a given URL
        """
        return self.send(url, method='get', **kwargs)

    def post(self, url, data, **kwargs):
        """
        Issue relentless POST request to given URL
        """
        return self.send(url, method='post', data=data, **kwargs)

    def put(self, url, data, **kwargs):
        """
        Issue relentless POST request to a given URL
        """
        return self.send(url, method='put', data=data, **kwargs)

    def send(self, url, method, data=None, **kwargs):
        """
        Issue relentless request to a given URL
        """
        return self._send_request_repeatedly(url, method=method, data=data,
                                             **kwargs)

    def _send_request(self, url, method, data=None, **kwargs):
        files = kwargs.get("files", None)

        headers = kwargs.get("headers", None) or {}
        headers["User-Agent"] = self.user_agent
        if not files:
            headers["content-type"] = "application/json"

        try:
            req_args = {}
            req_args.update(kwargs)
            if self.cert:
                req_args["cert"] = self.cert
            elif self.auth:
                req_args["auth"] = self.auth
            req_args["headers"] = headers
            req_args["timeout"] = self.timeout
            method = method.lower()
            if method in ["post", "put", "patch"]:
                req_args["data"] = data if files else json.dumps(data)
            response = requests.request(method, url, **req_args)
        except requests.RequestException as ex:
            raise RequestRetryError(
                "Requests error on {}: {}".format(url, str(ex)))

        if response.status_code >= 500:
            # Server error.  Hopefully this is only temporary problem, we wan't
            # to re-try, and wait till the server works again.
            raise RequestRetryError(
                "Request server error on {}: {} {}".format(
                    url, response.status_code, response.reason))

        if response.status_code == 408:
            # 408 Request Timeout - We couldn't send the request within
            # a reasonable time-frame, so let's retry.
            raise RequestRetryError(
                f"Request Timeout {response.status_code}: {response.reason}: {url}")

        if response.status_code >= 400:
            # Client error.  The mistake is on our side, it doesn't make sense
            # to continue with retries.
            msg = "Request client error on {}: {} {}".format(
                    url, response.status_code, response.reason)
            raise RequestError(msg, response)

        # TODO: Success, but tighten the redirects etc.
        return response

    def _send_request_repeatedly(self, url, method, data=None, **kwargs):
        """
        Repeat the request until it succeeds, or number of attempts is reached.
        """
        sleep = self.SLEEP_INCREMENT_TIME
        i = 0
        while True:
            i += 1
            if not self.try_indefinitely and i > self.attempts:
                raise RequestError(
                    "The server didn't return a successful response "
                    "(we gave it {} attempts)".format(self.attempts))

            try:
                return self._send_request(url, method=method, data=data,
                                          **kwargs)
            except RequestRetryError as ex:
                self.log.warning("Retry request #%s on %s: %s", i, url,
                                 str(ex))
                time.sleep(sleep)
                sleep += self.SLEEP_INCREMENT_TIME


class RequestRetryError(Exception):
    """
    Request to server failed, try again
    """


class RequestError(Exception):
    """
    Request to server failed
    """

    def __init__(self, message, response=None):
        super().__init__(message)
        self.response = response
