# authentic2 - versatile identity manager
# Copyright (C) 2010-2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import pytest

from authentic2_idp_oidc import app_settings as oidc_app_settings
from authentic2_idp_oidc.models import OIDCClaim, OIDCClient
from tests.utils import login


@pytest.fixture
def app(app, admin):
    login(app, admin)
    return app


@pytest.fixture
def superuser_app(app, superuser):
    login(app, superuser)
    return app


def test_add_oidc_service_superuser(superuser_app):
    resp = superuser_app.get('/manage/services/')
    assert 'Add OIDC service' in resp.text
    assert OIDCClient.objects.count() == 0
    assert OIDCClaim.objects.count() == 0

    resp = resp.click('Add OIDC service')
    form = resp.form
    form['name'] = 'Test'
    form['redirect_uris'] = 'http://example.com'
    form['has_api_access'] = True
    form['activate_user_profiles'] = True
    resp = form.submit()

    assert OIDCClient.objects.count() == 1
    assert OIDCClaim.objects.count() == len(oidc_app_settings.DEFAULT_MAPPINGS)
    oidc_client = OIDCClient.objects.get()
    assert oidc_client.has_api_access is True
    assert oidc_client.activate_user_profiles is True
    assert resp.location == f'/manage/services/{oidc_client.pk}/'
    resp = resp.follow()
    assert 'Settings' in resp.text
    assert 'Delete' in resp.text


def test_add_oidc_service_admin(app):
    resp = app.get('/manage/services/')
    assert 'Add OIDC service' in resp.text
    assert OIDCClient.objects.count() == 0
    assert OIDCClaim.objects.count() == 0

    resp = resp.click('Add OIDC service')
    form = resp.form
    form['name'] = 'Test'
    form['redirect_uris'] = 'http://example.com'
    assert 'has_api_access' not in form.fields
    assert 'activate_user_profiles' not in form.fields
    resp = form.submit()

    assert OIDCClient.objects.count() == 1
    assert OIDCClaim.objects.count() == len(oidc_app_settings.DEFAULT_MAPPINGS)
    oidc_client = OIDCClient.objects.get()
    assert oidc_client.has_api_access is False
    assert oidc_client.activate_user_profiles is False
    assert resp.location == f'/manage/services/{oidc_client.pk}/'
    resp = resp.follow()
    assert 'Settings' in resp.text
    assert 'Delete' in resp.text


class TestEdit:
    @pytest.fixture(autouse=True)
    def oidc_client(self, db):
        return OIDCClient.objects.create(name='Test', slug='test', redirect_uris='http://example.com')

    def test_edit(self, app, oidc_client):
        resp = app.get('/manage/services/')
        resp = resp.click('Test')
        resp = resp.click('Settings')
        assert resp.pyquery('.service-field--value')
        for value in resp.pyquery('.service-field--value'):
            assert '\n' not in value.text
            assert not value.text.endswith(' ')
        resp = resp.click('Edit')

        # check breadcrumbs
        crumbs = [crumb for crumb in resp.pyquery('#breadcrumb')[0]]
        assert len(crumbs) == 6

        assert crumbs[0].text == 'Homepage'
        assert crumbs[0].items() == [('href', '/')]

        assert crumbs[1].text == 'Administration'
        assert crumbs[1].items() == [('href', '/manage/')]

        assert crumbs[2].text == 'Services'
        assert crumbs[2].items() == [('href', '/manage/services/')]

        assert crumbs[3].text == 'Test'
        assert crumbs[3].items() == [('href', f'/manage/services/{oidc_client.id}/')]

        assert crumbs[4].text == 'Configuration'
        assert crumbs[4].items() == [('href', f'/manage/services/{oidc_client.id}/settings/')]

        assert not crumbs[5].text
        assert crumbs[5].items() == [('href', '#')]

        form = resp.form
        form['name'] = 'New Test'
        form['colour'] = '#ff00ff'
        resp = form.submit()
        assert resp.location == '..'
        resp = resp.follow()
        assert 'New Test' in resp.text
        assert '#ff00ff' in resp.text

        resp = app.get(f'/manage/services/{oidc_client.id}/settings/edit/')
        form = resp.form

        form.set('authorization_mode', str(OIDCClient.AUTHORIZATION_MODE_NONE))
        form.set('always_save_authorization', True)

        resp = form.submit()
        assert 'errors processing your form' in resp.pyquery('.errornotice p')[0].text
        assert (
            'Cannot save user authorizations when authorization mode is none.'
            in resp.pyquery('.error p')[0].text
        )
        oidc_client.refresh_from_db()
        assert oidc_client.authorization_mode != OIDCClient.AUTHORIZATION_MODE_NONE
        assert not oidc_client.always_save_authorization

        form = resp.form
        form.set('authorization_mode', str(OIDCClient.AUTHORIZATION_MODE_BY_SERVICE))
        resp = form.submit().follow()
        assert not resp.pyquery('.errornotice p')
        assert not resp.pyquery('.error p')

        resp = app.get(f'/manage/services/{oidc_client.id}/settings/edit/')
        form = resp.form
        form[
            'redirect_uris'
        ] = '''http://example.com
http://example2.com'''
        form['sector_identifier_uri'] = ''
        resp = form.submit()
        assert 'errors processing your form' in resp.pyquery('.errornotice p')[0].text
        assert (
            'Cannot save redirect URIs bearing different domains if no sector identifier URI is provided.'
            in resp.pyquery('.error p')[0].text
        )

        form = resp.form
        form[
            'redirect_uris'
        ] = '''http://example.com
https://example.com/auth
http://example.com/misc/auth2'''
        resp = form.submit().follow()
        assert not resp.pyquery('.errornotice p')
        assert not resp.pyquery('.error p')

        resp = app.get(f'/manage/services/{oidc_client.id}/settings/edit/')
        form = resp.form
        form[
            'redirect_uris'
        ] = '''http://example.com
http://example2.com'''
        form['sector_identifier_uri'] = 'example.com'
        resp = form.submit().follow()
        assert not resp.pyquery('.errornotice p')
        assert not resp.pyquery('.error p')

        resp = app.get(f'/manage/services/{oidc_client.id}/settings/edit/')
        form = resp.form
        form['sector_identifier_uri'] = 'example2.com'
        resp = form.submit().follow()
        assert (
            'Changing the sector identifier modifies the subject identifiers for each user!'
            == resp.pyquery('.messages .warning').text()
        )

        resp = app.get(f'/manage/services/{oidc_client.id}/settings/edit/')
        form = resp.form
        form['slug'] = 'anewslug'
        resp = form.submit().follow()
        assert not resp.pyquery('.errornotice p')
        assert not resp.pyquery('.error p')
        oidc_client.refresh_from_db()
        assert oidc_client.slug == 'anewslug'

        resp = app.get(f'/manage/services/{oidc_client.id}/settings/edit/')
        form = resp.form
        form['slug'] = 'anew Invalid! slug'
        resp = form.submit()
        assert 'errors processing your form' in resp.pyquery('.errornotice p')[0].text
        assert (
            'Enter a valid “slug” consisting of letters, numbers, underscores or hyphens.'
            in resp.pyquery('.error p')[0].text
        )

    def test_delete(self, app):
        resp = app.get('/manage/services/')
        resp = resp.click('Test')
        resp = resp.click('Delete')
        resp = resp.form.submit().follow()
        assert OIDCClient.objects.count() == 0

    def test_add_claim(self, app, oidc_client):
        resp = app.get(f'/manage/services/{oidc_client.pk}/settings/')
        resp = resp.click('Add claim')
        form = resp.form
        form['name'] = 'claim'
        form['value'] = 'value'
        form['scopes'] = 'profile'
        resp = form.submit()
        assert resp.location == f'/manage/services/{oidc_client.pk}/'
        assert OIDCClaim.objects.filter(
            client=oidc_client, name='claim', value='value', scopes='profile'
        ).exists()

    def test_add_claim_mandatory_field_name(self, app, oidc_client):
        resp = app.get(f'/manage/services/{oidc_client.pk}/settings/')
        resp = resp.click('Add claim')
        form = resp.form
        form['value'] = 'value'
        form['scopes'] = 'profile'
        resp = form.submit()
        assert len(resp.pyquery('.error')) == 1
        assert 'This field is required.' in resp.pyquery('.error').text()
        assert not OIDCClaim.objects.filter(client=oidc_client, name='claim', value='value', scopes='profile')

    def test_add_claim_mandatory_field_value(self, app, oidc_client):
        resp = app.get(f'/manage/services/{oidc_client.pk}/settings/')
        resp = resp.click('Add claim')
        form = resp.form
        form['name'] = 'claim'
        form['scopes'] = 'profile'
        resp = form.submit()
        assert len(resp.pyquery('.error')) == 1
        assert 'This field is required.' in resp.pyquery('.error').text()
        assert not OIDCClaim.objects.filter(client=oidc_client, name='claim', value='value', scopes='profile')

    def test_add_claim_mandatory_field_scope(self, app, oidc_client):
        resp = app.get(f'/manage/services/{oidc_client.pk}/settings/')
        resp = resp.click('Add claim')
        form = resp.form
        form['name'] = 'claim'
        form['value'] = 'value'
        resp = form.submit()
        assert len(resp.pyquery('.error')) == 1
        assert 'This field is required.' in resp.pyquery('.error').text()
        assert not OIDCClaim.objects.filter(client=oidc_client, name='claim', value='value', scopes='profile')

    def test_add_claim_redundancy_error(self, app, oidc_client):
        resp = app.get(f'/manage/services/{oidc_client.pk}/settings/')
        OIDCClaim.objects.create(
            client=oidc_client, name='yet_another_claim', value='value', scopes='profile'
        )

        resp = resp.click('Add claim')
        form = resp.form
        form['name'] = 'yet_another_claim'
        form['value'] = 'value'
        form['scopes'] = 'profile'
        resp = form.submit()
        assert len(resp.pyquery('.error')) == 1
        assert 'claim name is already defined for this client' in resp.pyquery('.error').text()
        assert (
            OIDCClaim.objects.filter(
                client=oidc_client, name='yet_another_claim', value='value', scopes='profile'
            ).count()
            == 1
        )

    class TestEditClaim:
        @pytest.fixture(autouse=True)
        def claim(self, oidc_client):
            return OIDCClaim.objects.create(client=oidc_client, name='claim', value='value', scopes='profile')

        def test_edit_claim(self, app, oidc_client, claim):
            resp = app.get(f'/manage/services/{oidc_client.pk}/settings/')
            assert 'claim' in resp.text
            resp = resp.click('Edit', index=1)
            form = resp.form
            form['value'] = 'new value'
            resp = form.submit()
            assert resp.location == f'/manage/services/{oidc_client.pk}/'
            claim.refresh_from_db()
            assert claim.value == 'new value'

        def test_delete_claim(self, app, oidc_client):
            resp = app.get(f'/manage/services/{oidc_client.pk}/settings/')
            assert 'claim' in resp.text
            resp = resp.click('Delete')
            form = resp.form
            resp = form.submit()
            assert resp.location == f'/manage/services/{oidc_client.pk}/'
            assert OIDCClaim.objects.filter(client=oidc_client).count() == 0
