# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals

#################################################################################################

import time
from urllib.parse import quote

import requests

from ..helper.utils import JsonDebugPrinter
from ..helper import LazyLogger
from ..helper.exceptions import HTTPException
from .utils import clean_none_dict_values

#################################################################################################

LOG = LazyLogger(__name__)

#################################################################################################


class HTTP(object):

    session = None
    keep_alive = False

    def __init__(self, client):

        self.client = client
        self.config = client.config

    def start_session(self):

        self.session = requests.Session()

        max_retries = self.config.data["http.max_retries"]
        self.session.mount(
            "http://", requests.adapters.HTTPAdapter(max_retries=max_retries)
        )
        self.session.mount(
            "https://", requests.adapters.HTTPAdapter(max_retries=max_retries)
        )

    def stop_session(self):

        if self.session is None:
            return

        try:
            LOG.info("--<[ session/%s ]", id(self.session))
            self.session.close()
        except Exception as error:
            LOG.warning("The requests session could not be terminated: %s", error)

    def _replace_user_info(self, string):

        if "{server}" in string:
            if self.config.data.get("auth.server", None):
                string = string.replace("{server}", self.config.data["auth.server"])
            else:
                LOG.debug("Server address not set")

        if "{UserId}" in string:
            if self.config.data.get("auth.user_id", None):
                string = string.replace("{UserId}", self.config.data["auth.user_id"])
            else:
                LOG.debug("UserId is not set.")

        return string

    def request(self, data, session=None):
        """Give a chance to retry the connection. Jellyfin sometimes can be slow to answer back
        data dictionary can contain:
        type: GET, POST, etc.
        url: (optional)
        handler: not considered when url is provided (optional)
        params: request parameters (optional)
        json: request body (optional)
        headers: (optional),
        verify: ssl certificate, True (verify using device built-in library) or False
        """
        if not data:
            raise AttributeError("Request cannot be empty")

        data = self._request(data)
        LOG.debug("--->[ http ] %s", JsonDebugPrinter(data))
        retry = data.pop("retry", 5)

        while True:

            try:
                r = self._requests(
                    session or self.session or requests, data.pop("type", "GET"), **data
                )
                r.content  # release the connection

                if not self.keep_alive and self.session is not None:
                    self.stop_session()

                r.raise_for_status()

            except requests.exceptions.ConnectionError as error:
                if retry:

                    retry -= 1
                    time.sleep(1)

                    continue

                LOG.error(error)
                self.client.callback(
                    "ServerUnreachable",
                    {"ServerId": self.config.data["auth.server-id"]},
                )

                raise HTTPException("ServerUnreachable", error)

            except requests.exceptions.ReadTimeout as error:
                if retry:

                    retry -= 1
                    time.sleep(1)

                    continue

                LOG.error(error)

                raise HTTPException("ReadTimeout", error)

            except requests.exceptions.HTTPError as error:
                LOG.error(error)

                if r.status_code == 401:

                    if "X-Application-Error-Code" in r.headers:
                        self.client.callback(
                            "AccessRestricted",
                            {"ServerId": self.config.data["auth.server-id"]},
                        )

                        raise HTTPException("AccessRestricted", error)
                    else:
                        self.client.callback(
                            "Unauthorized",
                            {"ServerId": self.config.data["auth.server-id"]},
                        )
                        self.client.auth.revoke_token()

                        raise HTTPException("Unauthorized", error)

                elif r.status_code == 400:
                    LOG.warning(error)
                    LOG.warning(data)
                    try:
                        LOG.warning(r.json())
                    except Exception:
                        LOG.warning(r.text)

                elif r.status_code == 500:  # log and ignore.
                    LOG.error("--[ 500 response ] %s", error)

                    return

                elif r.status_code == 502:
                    if retry:

                        retry -= 1
                        time.sleep(1)

                        continue

                raise HTTPException(r.status_code, error)

            except requests.exceptions.MissingSchema as error:
                LOG.error("Request missing Schema. " + str(error))
                raise HTTPException(
                    "MissingSchema", {"Id": self.config.data.get("auth.server", "None")}
                )

            else:
                try:
                    self.config.data["server-time"] = r.headers.get("Date")
                    elapsed = int(r.elapsed.total_seconds() * 1000)
                    response = r.json()
                    LOG.debug("---<[ http ][%s ms]", elapsed)
                    LOG.debug(JsonDebugPrinter(response))

                    return clean_none_dict_values(response)
                except ValueError:
                    return
                except TypeError:
                    # Empty json
                    return

    def _request(self, data):

        if "url" not in data:
            data["url"] = "%s/%s" % (
                self.config.data.get("auth.server", ""),
                data.pop("handler", ""),
            )

        self._get_header(data)
        data["timeout"] = data.get("timeout") or self.config.data["http.timeout"]
        data["verify"] = data.get("verify") or self.config.data.get("auth.ssl", False)
        data["url"] = self._replace_user_info(data["url"])
        self._process_params(data.get("params") or {})
        self._process_params(data.get("json") or {})

        return data

    def _process_params(self, params):

        for key in params:
            value = params[key]

            if isinstance(value, dict):
                self._process_params(value)

            if isinstance(value, str):
                params[key] = self._replace_user_info(value)

    def _get_header(self, data):

        data["headers"] = data.setdefault("headers", {})

        if not data["headers"]:
            data["headers"].update(
                {
                    "Content-type": "application/json",
                    "Accept-Charset": "UTF-8,*",
                    "Accept-encoding": "gzip",
                    "User-Agent": self.config.data["http.user_agent"]
                    or "%s/%s"
                    % (
                        self.config.data.get("app.name", "Jellyfin for Kodi"),
                        self.config.data.get("app.version", "0.0.0"),
                    ),
                }
            )

        if "Authorization" not in data["headers"]:
            self._authorization(data)

        return data

    def _authorization(self, data):

        auth = "MediaBrowser "
        auth += "Client=%s, " % quote(
            self.config.data.get("app.name", "Jellyfin for Kodi"), safe=""
        )
        auth += "Device=%s, " % quote(
            self.config.data.get("app.device_name", "Unknown Device"), safe=""
        )
        auth += "DeviceId=%s, " % quote(
            self.config.data.get("app.device_id", "Unknown Device id"), safe=""
        )
        auth += "Version=%s" % quote(
            self.config.data.get("app.version", "0.0.0"), safe=""
        )

        data["headers"].update({"Authorization": auth})

        if self.config.data.get("auth.token") and self.config.data.get("auth.user_id"):

            auth += ", UserId=%s" % quote(self.config.data.get("auth.user_id"), safe="")
            auth += ", Token=%s" % quote(self.config.data.get("auth.token"), safe="")
            data["headers"].update(
                {
                    "Authorization": auth,
                }
            )

        return data

    def _requests(self, session, action, **kwargs):

        if action == "GET":
            return session.get(**kwargs)
        elif action == "POST":
            return session.post(**kwargs)
        elif action == "HEAD":
            return session.head(**kwargs)
        elif action == "DELETE":
            return session.delete(**kwargs)
