# -*- coding: utf-8 -*-

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

import requests
import xml.etree.ElementTree as etree

import xbmcgui

from utils import logging, settings, window
import clientinfo

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

# Disable annoying requests warnings
import requests.packages.urllib3
requests.packages.urllib3.disable_warnings()

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


@logging
class DownloadUtils():
    """
    Manages any up/downloads with PKC. Careful to initiate correctly
    Use startSession() to initiate.
    If not initiated, e.g. SSL check will fallback to False
    """

    # Borg - multiple instances, shared state
    _shared_state = {}

    # Requests session
    timeout = 30

    def __init__(self):
        self.__dict__ = self._shared_state

    def setUsername(self, username):
        """
        Reserved for userclient only
        """
        self.username = username
        self.logMsg("Set username: %s" % username, 0)

    def setUserId(self, userId):
        """
        Reserved for userclient only
        """
        self.userId = userId
        self.logMsg("Set userId: %s" % userId, 0)

    def setServer(self, server):
        """
        Reserved for userclient only
        """
        self.server = server
        self.logMsg("Set server: %s" % server, 0)

    def setToken(self, token):
        """
        Reserved for userclient only
        """
        self.token = token
        if token == '':
            self.logMsg('Set token: empty token!', 0)
        else:
            self.logMsg("Set token: xxxxxxx", 0)

    def setSSL(self, verifySSL=None, certificate=None):
        """
        Reserved for userclient only

        verifySSL must be 'true' to enable certificate validation

        certificate must be path to certificate or 'None'
        """
        if verifySSL is None:
            verifySSL = settings('sslverify')
        if certificate is None:
            certificate = settings('sslcert')
        self.logMsg("Verify SSL certificates set to: %s" % verifySSL, 0)
        self.logMsg("SSL client side certificate set to: %s" % certificate, 0)
        if verifySSL != 'true':
            self.s.verify = False
        if certificate != 'None':
            self.s.cert = certificate

    def startSession(self):
        """
        User should be authenticated when this method is called (via
        userclient)
        """
        # Start session
        self.s = requests.Session()

        client = clientinfo.ClientInfo()
        self.deviceId = client.getDeviceId()
        # Attach authenticated header to the session
        self.s.headers = client.getXArgsDeviceInfo()
        self.s.encoding = 'utf-8'
        # Set SSL settings
        self.setSSL()

        # Set other stuff
        self.setServer(window('pms_server'))
        self.setToken(window('pms_token'))
        self.setUserId(window('currUserId'))
        self.setUsername(window('plex_username'))

        # Retry connections to the server
        self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
        self.s.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))

        self.logMsg("Requests session started on: %s" % self.server, 0)

    def stopSession(self):
        try:
            self.s.close()
        except:
            self.logMsg("Requests session could not be terminated.", 0)
        try:
            del self.s
        except:
            pass
        self.logMsg('Request session stopped', 0)

    def getHeader(self, options=None):
        header = clientinfo.ClientInfo().getXArgsDeviceInfo()
        if options is not None:
            header.update(options)
        return header

    def __doDownload(self, s, type, **kwargs):
        if type == "GET":
            r = s.get(**kwargs)
        elif type == "POST":
            r = s.post(**kwargs)
        elif type == "DELETE":
            r = s.delete(**kwargs)
        elif type == "OPTIONS":
            r = s.options(**kwargs)
        elif type == "PUT":
            r = s.put(**kwargs)
        return r

    def downloadUrl(self, url, type="GET", postBody=None, parameters=None,
                    authenticate=True, headerOptions=None, verifySSL=True,
                    dontSignout=False):
        """
        Override SSL check with verifySSL=False

        If authenticate=True, existing request session will be used/started
        Otherwise, 'empty' request will be made

        Returns:
            False              If an error occured
            True               If connection worked but no body was received
            401, ...           integer if PMS answered with HTTP error 401
                               (unauthorized) or other http error codes
            xml                xml etree root object, if applicable
            JSON               json() object, if applicable
        """
        kwargs = {}
        if authenticate:
            # Get requests session
            try:
                s = self.s
            except AttributeError:
                self.logMsg("Request session does not exist: start one", 0)
                self.startSession()
                s = self.s
            # Replace for the real values
            url = url.replace("{server}", self.server)
        else:
            # User is not (yet) authenticated. Used to communicate with
            # plex.tv and to check for PMS servers
            s = requests
            headerOptions = self.getHeader(options=headerOptions)
            kwargs['timeout'] = self.timeout
            if settings('sslcert') != 'None':
                kwargs['cert'] = settings('sslcert')

        # Set the variables we were passed (fallback to request session
        # otherwise - faster)
        kwargs['url'] = url
        if verifySSL is False:
            kwargs['verify'] = False
        if headerOptions is not None:
            kwargs['headers'] = headerOptions
        if postBody is not None:
            kwargs['data'] = postBody
        if parameters is not None:
            kwargs['params'] = parameters

        # ACTUAL DOWNLOAD HAPPENING HERE
        try:
            r = self.__doDownload(s, type, **kwargs)

        # THE EXCEPTIONS
        except requests.exceptions.ConnectionError as e:
            # Connection error
            if dontSignout is False:
                self.logMsg("Server unreachable at: %s" % url, -1)
                self.logMsg(e, 2)
                # Make the addon aware of status
                window('emby_online', value="false")
            return False

        except requests.exceptions.ConnectTimeout as e:
            self.logMsg("Server timeout at: %s" % url, -1)
            self.logMsg(e, 2)
            return False

        except requests.exceptions.HTTPError as e:
            r = r.status_code
            if r == 401:
                # Unauthorized
                self.logMsg('Error 401 contacting %s' % url, -1)
            elif r in (301, 302):
                # Redirects
                self.logMsg('HTTP redirect error %s at %s' % (r, url), -1)
            elif r == 400:
                # Bad requests
                self.logMsg('Bad request at %s' % url, -1)
            else:
                self.logMsg('HTTP Error %s at %s' % (r, url), -1)
            self.logMsg(e, 2)
            return r

        except requests.exceptions.SSLError as e:
            self.logMsg("Invalid SSL certificate for: %s" % url, -1)
            self.logMsg(e, 2)
            return False

        except requests.exceptions.RequestException as e:
            self.logMsg("Unknown error connecting to: %s" % url, -1)
            self.logMsg("Error message: %s" % e, 2)
            return False

        except SystemExit:
            self.logMsg('SystemExit detected, aborting download', 0)
            self.stopSession()
            return False

        except:
            self.logMsg('Unknown requests error', -1)
            import traceback
            self.logMsg(traceback.format_exc(), 0)
            return False

        # THE RESPONSE #####
        if r.status_code == 204:
            # No body in the response
            return True

        elif r.status_code == 401:
            if dontSignout is True:
                # Called when checking a connect - no need for rash action
                return 401
            r.encoding = 'utf-8'
            self.logMsg('HTTP error 401 from PMS. Message received:', -1)
            self.logMsg(r.text, -1)
            if '401 Unauthorized' in r.text:
                # Truly unauthorized
                self.logMsg('We seem to be truely unauthorized', 0)
                if window('emby_serverStatus') not in ('401', 'Auth'):
                    # Tell userclient token has been revoked.
                    self.logMsg('Setting emby_serverStatus to 401', 0)
                    window('emby_serverStatus', value="401")
                    xbmcgui.Dialog().notification(
                        self.addonName,
                        "Error connecting: Unauthorized.",
                        xbmcgui.NOTIFICATION_ERROR)
                return 401
            else:
                # there might be other 401 where e.g. PMS is under strain (tbv)
                self.logMsg('PMS might only be under strain', 0)
                return 401

        elif r.status_code in (200, 201):
            # 200: OK
            # 201: Created
            try:
                # xml response
                r = etree.fromstring(r.content)
                return r
            except:
                r.encoding = 'utf-8'
                if r.text == '':
                    # Answer does not contain a body (even though it should)
                    return True
                try:
                    # UNICODE - JSON object
                    r = r.json()
                    return r
                except:
                    r.encoding = 'utf-8'
                    if '200 OK' in r.text:
                        # Received fucked up OK from PMS on playstate update
                        pass
                    else:
                        self.logMsg("Unable to convert the response for: %s"
                                    % url, -1)
                        self.logMsg("Received headers were: %s"
                                    % r.headers, -1)
                        self.logMsg('Received text:', -1)
                        self.logMsg(r.text, -1)
                    return True
        else:
            self.logMsg('Unknown answer from PMS %s with status code %s. '
                        'Message:' % (url, r.status_code), -1)
            r.encoding = 'utf-8'
            self.logMsg(r.text, -1)
            return True