import logging
from urllib.parse import parse_qs
from urllib.parse import urlsplit

from canaille.app import models

from . import client_credentials


def test_no_logged_no_access(testclient):
    testclient.get("/consent", status=403)


def test_revokation(testclient, client, consent, logged_user, token, backend, caplog):
    res = testclient.get("/consent", status=200)
    res.mustcontain(client.client_name)
    res.mustcontain("Revoke access")
    res.mustcontain(no="Restore access")
    assert not consent.revoked
    assert not token.revoked

    res = testclient.get(f"/consent/revoke/{consent.consent_id}", status=302)
    assert ("success", "The access has been revoked") in res.flashes
    assert (
        "canaille",
        logging.SECURITY,
        f"Consent revoked for {logged_user.user_name} in client {client.client_name}",
    ) in caplog.record_tuples
    res = res.follow(status=200)
    res.mustcontain(no="Revoke access")
    res.mustcontain("Restore access")

    backend.reload(consent)
    assert consent.revoked
    backend.reload(token)
    assert token.revoked


def test_revokation_already_revoked(testclient, client, consent, logged_user, backend):
    consent.revoke()

    assert consent.revoked

    res = testclient.get(f"/consent/revoke/{consent.consent_id}", status=302)
    assert ("error", "The access is already revoked") in res.flashes
    res = res.follow(status=200)

    backend.reload(consent)
    assert consent.revoked


def test_restoration(testclient, client, consent, logged_user, token, backend):
    consent.revoke()

    assert consent.revoked
    backend.reload(token)
    assert token.revoked

    res = testclient.get(f"/consent/restore/{consent.consent_id}", status=302)
    assert ("success", "The access has been restored") in res.flashes
    res = res.follow(status=200)

    backend.reload(consent)
    assert not consent.revoked
    backend.reload(token)
    assert token.revoked


def test_restoration_already_restored(testclient, client, consent, logged_user, token):
    assert not consent.revoked

    res = testclient.get(f"/consent/restore/{consent.consent_id}", status=302)
    assert ("error", "The access is not revoked") in res.flashes
    res = res.follow(status=200)


def test_invalid_consent_revokation(testclient, client, logged_user):
    res = testclient.get("/consent/revoke/invalid", status=302)
    assert ("success", "The access has been revoked") not in res.flashes
    assert ("error", "Could not revoke this access") in res.flashes


def test_someone_else_consent_revokation(testclient, client, consent, logged_moderator):
    res = testclient.get(f"/consent/revoke/{consent.consent_id}", status=302)
    assert ("success", "The access has been revoked") not in res.flashes
    assert ("error", "Could not revoke this access") in res.flashes


def test_invalid_consent_restoration(testclient, client, logged_user):
    res = testclient.get("/consent/restore/invalid", status=302)
    assert ("success", "The access has been restored") not in res.flashes
    assert ("error", "Could not restore this access") in res.flashes


def test_someone_else_consent_restoration(
    testclient, client, consent, logged_moderator
):
    res = testclient.get(f"/consent/restore/{consent.consent_id}", status=302)
    assert ("success", "The access has been restore") not in res.flashes
    assert ("error", "Could not restore this access") in res.flashes


def test_oidc_authorization_after_revokation(
    testclient, logged_user, client, consent, backend
):
    consent.revoke()

    assert consent.revoked

    res = testclient.get(
        "/oauth/authorize",
        params=dict(
            response_type="code",
            client_id=client.client_id,
            scope="openid profile",
            nonce="somenonce",
            redirect_uri="https://client.test/redirect1",
        ),
        status=200,
    )

    res = res.form.submit(name="answer", value="accept", status=302)

    consents = backend.query(models.Consent, client=client, subject=logged_user)
    backend.reload(consent)
    assert consents[0] == consent
    assert not consent.revoked

    params = parse_qs(urlsplit(res.location).query)
    code = params["code"][0]
    res = testclient.post(
        "/oauth/token",
        params=dict(
            grant_type="authorization_code",
            code=code,
            scope="openid profile",
            redirect_uri=client.redirect_uris[0],
        ),
        headers={"Authorization": f"Basic {client_credentials(client)}"},
        status=200,
    )

    access_token = res.json["access_token"]
    token = backend.get(models.Token, access_token=access_token)
    assert token.client == client
    assert token.subject == logged_user


def test_trusted_client_appears_in_consent_list(
    testclient, client, logged_user, backend
):
    assert not client.trusted
    res = testclient.get("/consent/trusted-applications")
    res.mustcontain(no=client.client_name)

    client.client_uri = "https://client.trusted.test"
    backend.save(client)

    res = testclient.get("/consent/trusted-applications")
    res.mustcontain(client.client_name)


def test_revoke_trusted_client(testclient, client, logged_user, token, backend):
    client.client_uri = "https://client.trusted.test"
    backend.save(client)
    assert not backend.get(models.Consent)
    assert not token.revoked

    res = testclient.get(f"/consent/revoke-trusted/{client.client_id}", status=302)
    assert ("success", "The access has been revoked") in res.flashes

    consent = backend.get(models.Consent)
    assert consent.client == client
    assert consent.subject == logged_user
    assert consent.scope == ["openid", "email", "profile", "groups", "address", "phone"]
    assert not consent.issue_date
    backend.reload(token)
    assert token.revoked

    res = testclient.get(f"/consent/restore/{consent.consent_id}", status=302)
    assert ("success", "The access has been restored") in res.flashes

    backend.reload(consent)
    assert not consent.revoked
    assert consent.issue_date
    backend.reload(token)
    assert token.revoked

    res = testclient.get(f"/consent/revoke/{consent.consent_id}", status=302)
    assert ("success", "The access has been revoked") in res.flashes
    backend.reload(consent)
    assert consent.revoked
    assert consent.issue_date


def test_revoke_invalid_trusted_client(testclient, logged_user):
    res = testclient.get("/consent/revoke-trusted/invalid", status=302)
    assert ("error", "Could not revoke this access") in res.flashes


def test_revoke_trusted_client_with_manual_consent(
    testclient, logged_user, client, consent, backend
):
    client.client_uri = "https://client.trusted.test"
    backend.save(client)
    res = testclient.get(f"/consent/revoke-trusted/{client.client_id}", status=302)
    res = res.follow()
    assert ("success", "The access has been revoked") in res.flashes


def test_revoke_trusted_client_with_manual_revokation(
    testclient, logged_user, client, consent, backend
):
    client.client_uri = "https://client.trusted.test"
    backend.save(client)
    consent.revoke()
    backend.save(consent)

    res = testclient.get(f"/consent/revoke-trusted/{client.client_id}", status=302)
    res = res.follow()
    assert ("error", "The access is already revoked") in res.flashes


def test_tos_policy(testclient, logged_user, client, trusted_client, backend, caplog):
    """Tos and policy links must appear in the consent page."""
    res = testclient.get(
        "/oauth/authorize",
        params=dict(
            response_type="code",
            client_id=client.client_id,
            scope="openid profile email groups address phone",
            nonce="somenonce",
            tos_uri="https://client.test/tos",
            policy_uri="https://client.test/policy",
            redirect_uri="https://client.test/redirect1",
        ),
        status=200,
    )
    res.mustcontain("https://client.test/tos")
    res.mustcontain("https://client.test/policy")
