from __future__ import absolute_import
from xml.etree import ElementTree

from . import http
from . import exceptions
from . import plexobjects
from . import plexconnection
from . import util

RESOURCES = 'https://plex.tv/api/resources?includeHttps=1'


class PlexResource(object):
    def __init__(self, data):
        self.connection = None
        self.connections = []
        self.accessToken = None
        self.sourceType = None

        if data is None:
            return

        self.accessToken = data.attrib.get('accessToken')
        self.httpsRequired = data.attrib.get('httpsRequired') == '1'
        self.type = data.attrib.get('type')
        self.clientIdentifier = data.attrib.get('clientIdentifier')
        self.product = data.attrib.get('product')
        self.provides = data.attrib.get('provides', [])
        self.serverClass = data.attrib.get('serverClass')
        self.sourceType = data.attrib.get('sourceType')
        self.uuid = self.clientIdentifier

        if 'server' not in self.provides and self.product != "Plex Media Server":
            return

        hasSecureConn = False

        for conn in data.findall('Connection'):
            if conn.attrib.get('protocol') == "https":
                hasSecureConn = True
                break

        addLocalConsFound = []
        for conn in data.findall('Connection'):
            connection = plexconnection.PlexConnection(
                plexconnection.PlexConnection.SOURCE_MYPLEX,
                conn.attrib.get('uri'),
                conn.attrib.get('local') == '1',
                self.accessToken,
                hasSecureConn and conn.attrib.get('protocol') != "https"
            )

            # Keep the secure connection on top
            if connection.isSecure and not util.LOCAL_OVER_SECURE:
                self.connections.insert(0, connection)
            elif not connection.isSecure and util.LOCAL_OVER_SECURE:
                self.connections.insert(0, connection)
            else:
                self.connections.append(connection)

            if connection.isSecureButLocal:
                addLocalConsFound.append((connection.isSecureButLocal, connection.address))

            # If the connection is one of our plex.direct secure connections, add
            # the nonsecure variant as well, unless https is required.
            #
            if (self.httpsRequired and conn.attrib.get('protocol') == "https" and
                    conn.attrib.get('address') not in conn.attrib.get('uri') and conn.attrib.get('port')):
                self.connections.append(
                    plexconnection.PlexConnection(
                        plexconnection.PlexConnection.SOURCE_MYPLEX,
                        "http://{0}:{1}".format(conn.attrib.get('address'), conn.attrib.get('port')),
                        conn.attrib.get('local') == '1',
                        self.accessToken,
                        True
                    )
                )

        # add discovered local cons if necessary
        for ipPort, origAddress in addLocalConsFound:
            ip, port = ipPort
            if not port:
                continue

            address = "http://" + ip + ":" + str(port)
            for conn in self.connections:
                if conn.address == address:
                    continue

            util.DEBUG_LOG(
                "Secure connection {0} has a locally reachable IP, add it to the checklist".format(origAddress))
            lcon = plexconnection.PlexConnection(
                plexconnection.PlexConnection.SOURCE_DISCOVERED,
                "http://" + ip + ":" + str(port),
                True,
                self.accessToken,
                not util.LOCAL_OVER_SECURE,
                skipLocalCheck=True
            )
            if util.LOCAL_OVER_SECURE:
                self.connections.insert(0, lcon)
            else:
                self.connections.append(lcon)

    def __repr__(self):
        return '<{0}:{1}>'.format(self.__class__.__name__, self.name.encode('utf8'))


class ResourceConnection(plexobjects.PlexObject):
    # Constants
    STATE_UNKNOWN = "unknown"
    STATE_UNREACHABLE = "unreachable"
    STATE_REACHABLE = "reachable"
    STATE_UNAUTHORIZED = "unauthorized"
    STATE_INSECURE = "insecure_untested"

    SOURCE_MANUAL = 1
    SOURCE_DISCOVERED = 2
    SOURCE_MYPLEX = 4

    SCORE_REACHABLE = 4
    SCORE_LOCAL = 2
    SCORE_SECURE = 1

    def init(self, data):
        self.secure = True
        self.reachable = False
        self.data = None

    def __repr__(self):
        return '<{0}:{1}>'.format(self.__class__.__name__, self.uri.encode('utf8'))

    @property
    def http_uri(self):
        return 'http://{0}:{1}'.format(self.address, self.port)

    @property
    def URL(self):
        if self.secure:
            return self.uri
        else:
            return self.http_url

    def connect(self):
        util.LOG('Connecting: {0}', util.cleanToken(self.URL))
        try:
            self.data = self.query('/')
            self.reachable = True
            return True
        except Exception as err:
            util.ERROR(util.cleanToken(self.URL), err)

        util.LOG('Connecting: Secure failed, trying insecure...')
        self.secure = False

        try:
            self.data = self.query('/')
            self.reachable = True
            return True
        except Exception as err:
            util.ERROR(util.cleanToken(self.URL), err)

        return False

    def headers(self, token=None):
        headers = util.BASE_HEADERS.copy()
        if token:
            headers['X-Plex-Token'] = token
        return headers

    def query(self, path, method=None, token=None, **kwargs):
        method = method or http.requests.get
        url = self.getURL(path)
        util.LOG('{0} {1}', method.__name__.upper(), url)
        response = method(url, headers=self.headers(token), timeout=util.DEFAULT_TIMEOUT, **kwargs)
        if response.status_code not in (200, 201):
            codename = http.status_codes.get(response.status_code)[0]
            raise exceptions.BadRequest('({0}) {1}'.format(response.status_code, codename))
        data = response.text.encode('utf8')

        return ElementTree.fromstring(data) if data else None

    def getURL(self, path, token=None):
        if token:
            delim = '&' if '?' in path else '?'
            return '{base}{path}{delim}X-Plex-Token={token}'.format(base=self.URL, path=path, delim=delim, token=util.hideToken(token))
        return '{0}{1}'.format(self.URL, path)


class PlexResourceList(plexobjects.PlexItemList):
    def __init__(self, data, initpath=None, server=None):
        self._data = data
        self.initpath = initpath
        self._server = server
        self._items = None

    @property
    def items(self):
        if self._items is None:
            if self._data is not None:
                self._items = [PlexResource(elem, initpath=self.initpath, server=self._server) for elem in self._data]
            else:
                self._items = []

        return self._items


# fixme: never called
def fetchResources(token):
    headers = util.BASE_HEADERS.copy()
    headers['X-Plex-Token'] = token
    util.LOG('GET {0}?X-Plex-Token={1}', RESOURCES, util.hideToken(token))
    response = http.GET(RESOURCES)
    data = ElementTree.fromstring(response.text.encode('utf8'))
    from . import plexserver
    return [plexserver.PlexServer(elem) for elem in data]


# fixme: never called
def findResource(resources, search, port=32400):
    """ Searches server.name """
    search = search.lower()
    util.LOG('Looking for server: {0}', search)
    for server in resources:
        if search == server.name.lower():
            util.LOG('Server found: {0}', server)
            return server
    util.LOG('Unable to find server: {0}', search)
    raise exceptions.NotFound('Unable to find server: {0}'.format(search))


# fixme: never called
def findResourceByID(resources, ID):
    """ Searches server.clientIdentifier """
    util.LOG('Looking for server by ID: {0}', ID)
    for server in resources:
        if ID == server.clientIdentifier:
            util.LOG('Server found by ID: {0}', server)
            return server
    util.LOG('Unable to find server by ID: {0}', ID)
    raise exceptions.NotFound('Unable to find server by ID: {0}'.format(ID))
