import logging
import urllib
import requests

import rd

__version__ = '0.3'

RELS = {
    'activity_streams': 'http://activitystrea.ms/spec/1.0',
    'avatar': 'http://webfinger.net/rel/avatar',
    'hcard': 'http://microformats.org/profile/hcard',
    'open_id': 'http://specs.openid.net/auth/2.0/provider',
    'opensocial': 'http://ns.opensocial.org/2008/opensocial/activitystreams',
    'portable_contacts': 'http://portablecontacts.net/spec/1.0',
    'profile': 'http://webfinger.net/rel/profile-page',
    'xfn': 'http://gmpg.org/xfn/11',
}

WEBFINGER_TYPES = (
    'lrdd',                                 # current
    'http://lrdd.net/rel/descriptor',       # deprecated on 12/11/2009
    'http://webfinger.net/rel/acct-desc',   # deprecated on 11/26/2009
    'http://webfinger.info/rel/service',    # deprecated on 09/17/2009
)

UNOFFICIAL_ENDPOINTS = {
    'facebook.com': 'facebook-webfinger.appspot.com',
    'twitter.com': 'twitter-webfinger.appspot.com',
}

logger = logging.getLogger("webfinger")


class WebFingerException(Exception):
    pass


class WebFingerResponse(object):
    """ Response that wraps an RD object. This class provides a `secure`
        parameter to indicate whether the response was returned via HTTP or
        HTTPS. It also provides attribute-style access to links for specific
        rels, responding with the href attribute of the matched element.
    """

    def __init__(self, rd, secure=False):
        self.secure = secure
        self.rd = rd

    def __getattr__(self, name):
        if name in RELS:
            return self.rd.find_link(RELS[name], attr='href')
        return getattr(self.rd, name)


class WebFingerClient(object):

    def __init__(self, timeout=None, official=False):
        self._official = official
        self._session = requests.session(
            timeout=timeout,
            headers={'User-Agent': 'python-webfinger/%s' % __version__})

    def rd(self, url, raw=False):
        """ Load resource at given URL and attempt to parse either XRD or JRD
            based on HTTP response Content-Type header.
        """
        resp = self._session.get(url)
        content = resp.content
        return content if raw else rd.loads(content, resp.headers.get('Content-Type'))

    def hostmeta(self, host, resource=None, rel=None, secure=True, raw=False):
        """ Load host-meta resource from WebFinger provider.
            Defaults to a secure (SSL) connection unless secure=False.
        """

        protocol = "https" if secure else "http"

        # create querystring params
        params = {}
        if resource:
            params['resource'] = resource
        if rel:
            params['rel'] = rel

        # use unofficial endpoint, if enabled
        if not self._official and host in UNOFFICIAL_ENDPOINTS:
            host = UNOFFICIAL_ENDPOINTS[host]

        # create full host-meta URL
        url = "%s://%s/.well-known/host-meta" % (protocol, host)

        # attempt to load from host-meta.json resource
        resp = self._session.get("%s.json" % url, params=params)
        if resp.status_code == 404:
            # on failure, load from RFC 6415 host-meta resource
            resp = self._session.get(url, params=params, headers={"Accept": "application/json"})

        if resp.status_code != 200:
            # raise error if request was not successful
            raise WebFingerException("host-meta not found")

        # load XRD or JRD based on HTTP response Content-Type header
        content = resp.content
        return content if raw else rd.loads(content, resp.headers.get('Content-Type'))

    def finger(self, subject, resource=None, rel=None):
        """ Perform a WebFinger query based on the given subject.
            The `rel` parameter, if specified, will be passed to the provider,
            but be aware that providers are not required to implement the
            rel filter.
        """

        host = subject.split("@")[-1]

        try:
            # attempt SSL host-meta retrieval
            hm = self.hostmeta(host, resource=resource, rel=rel)
            secure = True
        except (requests.RequestException, requests.HTTPError):
            # on failure, attempt non-SSL
            hm = self.hostmeta(host, resource=resource, rel=rel, secure=False)
            secure = False

        if hm is None:
            raise WebFingerException("Unable to load or parse host-meta")

        if resource and hm.subject == resource:

            # resource query worked, return LRDD response
            return WebFingerResponse(hm, secure)

        else:

            # find template for LRDD document
            template = hm.find_link(WEBFINGER_TYPES, attr='template')
            secure = template.startswith('https://')

            url = template.replace('{uri}', urllib.quote_plus(subject))

            lrdd = self.rd(url)
            return WebFingerResponse(lrdd, secure)


def finger(subject, resource=None, rel=None, timeout=None, official=False):
    """ Shortcut method for invoking WebFingerClient.
    """

    if ":" not in subject:
        raise WebFingerException("scheme is required in subject URI")

    client = WebFingerClient(timeout=timeout, official=official)
    return client.finger(subject, resource=resource, rel=rel)


if __name__ == '__main__':

    import argparse

    parser = argparse.ArgumentParser(description="Simple webfinger client.")
    parser.add_argument("acct", metavar="URI", help="account URI")
    parser.add_argument("-d", "--debug", dest="debug", action="store_true", help="print debug logging output to console")
    parser.add_argument("-r", "--rel", metavar="REL", dest="rel", help="desired relation")

    args = parser.parse_args()

    if args.debug:
        logging.basicConfig(level=logging.DEBUG)

    wf = finger(args.acct, rel=args.rel)

    if args.rel:

        link = wf.find_link(args.rel)

        if link is None:
            print "*** Link not found for rel=%s" % args.rel

        print "%s:\n\t%s" % (link.rel, link.href)

    else:

        print "Activity Streams:  ", wf.activity_streams
        print "Avatar:            ", wf.avatar
        print "HCard:             ", wf.hcard
        print "OpenID:            ", wf.open_id
        print "Open Social:       ", wf.opensocial
        print "Portable Contacts: ", wf.portable_contacts
        print "Profile:           ", wf.profile
        print "XFN:               ", wf.find_link("http://gmpg.org/xfn/11", attr="href")

    if not wf.secure:
        print "\n*** Warning: Data was retrieved over an insecure connection"
