import time

import json
import pytest
import responses
from oic.oic import IdToken
from six.moves.urllib.parse import parse_qsl, urlencode, urlparse

from example.app import ISSUER1, ISSUER2, CLIENT1, CLIENT2
from .app import app, auth


class TestExampleApp(object):
    PROVIDER1_METADATA = {
        'issuer': ISSUER1,
        'authorization_endpoint': ISSUER1 + '/auth',
        'jwks_uri': ISSUER1 + '/jwks',
        'token_endpoint': ISSUER1 + '/token',
        'userinfo_endpoint': ISSUER1 + '/userinfo'
    }
    PROVIDER2_METADATA = {
        'issuer': ISSUER2,
        'authorization_endpoint': ISSUER2 + '/auth',
        'jwks_uri': ISSUER2 + '/jwks',
        'token_endpoint': ISSUER2 + '/token',
        'userinfo_endpoint': ISSUER2 + '/userinfo'
    }
    USER_ID = 'user1'

    @pytest.fixture('session', autouse=True)
    def setup(self):
        app.testing = True

        with responses.RequestsMock() as r:
            # mock provider discovery
            r.add(responses.GET, ISSUER1 + '/.well-known/openid-configuration', json=self.PROVIDER1_METADATA)
            r.add(responses.GET, ISSUER2 + '/.well-known/openid-configuration', json=self.PROVIDER2_METADATA)
            auth.init_app(app)

    @responses.activate
    def perform_authentication(self, test_client, login_endpoint, client_id, provider_metadata):
        # index page should make auth request
        auth_redirect = test_client.get(login_endpoint)
        parsed_auth_request = dict(parse_qsl(urlparse(auth_redirect.location).query))

        now = int(time.time())
        # mock token response
        id_token = IdToken(iss=provider_metadata['issuer'],
                           aud=client_id,
                           sub=self.USER_ID,
                           exp=now + 10,
                           iat=now,
                           nonce=parsed_auth_request['nonce'])
        token_response = {'access_token': 'test_access_token', 'token_type': 'Bearer', 'id_token': id_token.to_jwt()}
        responses.add(responses.POST, provider_metadata['token_endpoint'], json=token_response)

        # mock userinfo response
        userinfo = {'sub': self.USER_ID, 'name': 'Test User'}
        responses.add(responses.GET, provider_metadata['userinfo_endpoint'], json=userinfo)

        # fake auth response sent to redirect URI
        fake_auth_response = 'code=fake_auth_code&state={}'.format(parsed_auth_request['state'])
        logged_in_page = test_client.get('/redirect_uri?{}'.format(fake_auth_response), follow_redirects=True)
        result = json.loads(logged_in_page.data.decode('utf-8'))

        assert result['access_token'] == 'test_access_token'
        assert result['id_token'] == id_token.to_dict()
        assert result['userinfo'] == {'sub': self.USER_ID, 'name': 'Test User'}

    @pytest.mark.parametrize('login_endpoint, client_id, provider_metadata', [
        ('/', CLIENT1, PROVIDER1_METADATA),
        ('/login2', CLIENT2, PROVIDER2_METADATA),
    ])
    def test_login_logout(self, login_endpoint, client_id, provider_metadata):
        client = app.test_client()

        self.perform_authentication(client, login_endpoint, client_id, provider_metadata)

        response = client.get('/logout')
        assert response.data.decode('utf-8') == "You've been successfully logged out!"

    def test_error_view(self):
        client = app.test_client()

        auth_redirect = client.get('/')
        parsed_auth_request = dict(parse_qsl(urlparse(auth_redirect.location).query))

        # fake auth error response sent to redirect_uri
        error_auth_response = {
            'error': 'invalid_request',
            'error_description': 'test error',
            'state': parsed_auth_request['state']
        }
        error_page = client.get('/redirect_uri?{}'.format(urlencode(error_auth_response)), follow_redirects=True)

        assert json.loads(error_page.data.decode('utf-8')) == {
            'error': error_auth_response['error'],
            'message': error_auth_response['error_description']
        }