import cherrypy
import os
import logging
from jaraco.site import output as default_output, render


try:
    from openid.store.filestore import FileOpenIDStore
    from openid.server import server
    from openid.consumer import discover
    from openid.extensions import sreg
except ImportError:
    pass

from urllib.parse import urljoin

log = logging.getLogger(__name__)


def output(path, *args, **kwargs):
    return default_output('openid/{0}'.format(path), *args, **kwargs)


class OpenID:
    # todo: get this from cherrypy
    _base_url = 'http://drake.jaraco.com:8080/openid/'

    def __init__(self):
        store_loc = os.path.join(os.path.dirname(__file__), 'openid store')
        self.store = FileOpenIDStore(store_loc)
        self.openid = server.Server(self.store, self.endpoint_url)

    def relative_url(self, path):
        # TODO: ensure _base_url ends with /
        return urljoin(self._base_url, path)

    # todo: make these properties just decorators on the appropriate methods
    @property
    def endpoint_url(self):
        return self.relative_url('server/')

    @property
    def id_base_url(self):
        return self.relative_url('id/')

    @property
    def yadis_base_url(self):
        return self.relative_url('yadis/')

    @cherrypy.expose
    @output("id")
    def id(self, username):
        return render(
            endpoint_url=self.endpoint_url,
            yadis_url=urljoin(self.yadis_base_url, username),
            user_url=urljoin(self.id_base_url, username),
        )

    @cherrypy.expose
    @output("yadis", method="xml", content_type="application/xrds+xml")
    def yadis(self, username=None):
        return render(
            discover=discover,
            endpoint_url=self.endpoint_url,
            user_url=username and urljoin(self.id_base_url, username),
        )

    @cherrypy.expose
    def server(self, *args, **kwargs):
        try:
            params = cherrypy.request.params
            openid_request = self.openid.decodeRequest(params)
        except server.ProtocolError as openid_error:
            return self.handle_openid_response(openid_error)

        if openid_request is None:
            return output("about")(render)(endpoint_url=self.endpoint_url)

        if openid_request.mode in ["checkid_immediate", "checkid_setup"]:
            return self.check_id_request(openid_request)

        return self.handle_openid_response(self.openid.handleRequest(openid_request))

    def handle_openid_response(self, resp):
        try:
            web_resp = self.openid.encodeResponse(resp)
            body = web_resp.body or ''
            cherrypy.response.status = web_resp.code
            cherrypy.response.headers.update(web_resp.headers)
        except server.EncodingError as err:
            body = err.response.encodeToKVForm()
            cherrypy.response.status = 400
            cherrypy.response.headers['Content-Type'] = 'text/plain; charset=UTF-8'
        return body

    def check_id_request(self, request):
        authorized = cherrypy.request.login == request.identity
        trust_root = request.trust_root
        session = cherrypy.session
        request_params = dict({trust_root: request})
        session.setdefault('last_request', dict()).update(request_params)
        return self.handle_openid_response(request.answer(authorized))

    @cherrypy.expose
    def allow(self, *args, **kwargs):
        trust_root = kwargs['trust_root']
        openid_request = self.request_from_session(trust_root)
        if not openid_request:
            raise Exception("Last request could not be retrieved")
        if openid_request.idSelect():
            open_identity = urljoin(self.id_base_url, kwargs['identifier'])
        else:
            open_identity = openid_request.identity

        assert (
            len(set(['yes', 'no']).intersection(kwargs)) == 1
        ), "Expected yes or no in allow post"
        affirmative = 'yes' in kwargs
        openid_response = openid_request.answer(affirmative)

        if affirmative:
            self.add_sreg_fields(openid_request, kwargs, openid_response)

        if kwargs.get('remember', 'no') == 'yes':
            remember_value = ['never', 'always'][affirmative]
            key = open_identity, openid_request.trust_root
            cherrypy.session[key] = remember_value

        return self.handle_openid_response(openid_response)

    def add_sreg_fields(self, request, params, response):
        sreg_req = sreg.SRegRequest.fromOpenIDRequest(request)
        source_values = dict(
            # todo, get this from the identified user
            nickname='jaraco',
            email='jaraco@jaraco.com',
            fullname='Jason R. Coombs',
        )
        send_fields = params.get('sreg', dict()).get('send', dict())
        # send only the fields requested by 'yes'
        values = [
            (key, source_values[key])
            for key in send_fields
            if send_fields[key] == 'yes'
        ]
        values = dict(values)
        sreg_resp = sreg.SRegResponse.extractResponse(sreg_req, values)
        sreg_resp.toMessage(response.fields)

    def request_from_session(self, trust_root):
        last_request = cherrypy.session.get('last_request', {})
        return last_request.get(trust_root, None)


# disable the OpenID class if openid modules aren't available
if 'FileOpenIDStore' not in globals():
    OpenID = lambda: None  # type: ignore  # noqa
    log.error("Unable to import openid modules - OpenID support disabled")
