import datetime

import cryptography.exceptions
import pytest
from django import forms
from django.utils.http import http_date

from funkwhale_api.federation import keys, signing


def test_can_sign_and_verify_request(nodb_factories):
    private, public = nodb_factories["federation.KeyPair"]()
    auth = nodb_factories["federation.SignatureAuth"](key=private)
    request = nodb_factories["federation.SignedRequest"](auth=auth)
    prepared_request = request.prepare()
    assert "date" in prepared_request.headers
    assert "signature" in prepared_request.headers
    assert signing.verify(prepared_request, public) is None


def test_can_sign_and_verify_request_digest(nodb_factories):
    private, public = nodb_factories["federation.KeyPair"]()
    auth = nodb_factories["federation.SignatureAuth"](key=private)
    request = nodb_factories["federation.SignedRequest"](
        auth=auth, method="post", data=b"hello=world"
    )
    prepared_request = request.prepare()
    assert "date" in prepared_request.headers
    assert "digest" in prepared_request.headers
    assert "signature" in prepared_request.headers
    assert signing.verify(prepared_request, public) is None


def test_verify_fails_with_wrong_key(nodb_factories):
    wrong_private, wrong_public = nodb_factories["federation.KeyPair"]()
    request = nodb_factories["federation.SignedRequest"]()
    prepared_request = request.prepare()

    with pytest.raises(cryptography.exceptions.InvalidSignature):
        signing.verify(prepared_request, wrong_public)


def test_verify_fails_with_wrong_date(nodb_factories, now):
    too_old = now - datetime.timedelta(seconds=31)
    too_old = http_date(too_old.timestamp())
    private, public = nodb_factories["federation.KeyPair"]()
    auth = nodb_factories["federation.SignatureAuth"](key=private)
    request = nodb_factories["federation.SignedRequest"](
        auth=auth, headers={"Date": too_old}
    )
    prepared_request = request.prepare()

    with pytest.raises(forms.ValidationError):
        signing.verify(prepared_request, public)


def test_can_verify_django_request(factories, fake_request):
    private_key, public_key = keys.get_key_pair()
    signed_request = factories["federation.SignedRequest"](
        auth__key=private_key, auth__headers=["date"]
    )
    prepared = signed_request.prepare()
    django_request = fake_request.get(
        "/",
        **{
            "HTTP_DATE": prepared.headers["date"],
            "HTTP_SIGNATURE": prepared.headers["signature"],
        }
    )
    assert signing.verify_django(django_request, public_key) is None


def test_can_verify_django_request_digest(factories, fake_request):
    private_key, public_key = keys.get_key_pair()
    signed_request = factories["federation.SignedRequest"](
        auth__key=private_key,
        method="post",
        data=b"hello=world",
        auth__headers=["date", "digest"],
    )
    prepared = signed_request.prepare()
    django_request = fake_request.post(
        "/",
        **{
            "HTTP_DATE": prepared.headers["date"],
            "HTTP_DIGEST": prepared.headers["digest"],
            "HTTP_SIGNATURE": prepared.headers["signature"],
        }
    )

    assert signing.verify_django(django_request, public_key) is None


def test_can_verify_django_request_digest_failure(factories, fake_request):
    private_key, public_key = keys.get_key_pair()
    signed_request = factories["federation.SignedRequest"](
        auth__key=private_key,
        method="post",
        data=b"hello=world",
        auth__headers=["date", "digest"],
    )
    prepared = signed_request.prepare()
    django_request = fake_request.post(
        "/",
        **{
            "HTTP_DATE": prepared.headers["date"],
            "HTTP_DIGEST": prepared.headers["digest"] + "noop",
            "HTTP_SIGNATURE": prepared.headers["signature"],
        }
    )

    with pytest.raises(cryptography.exceptions.InvalidSignature):
        signing.verify_django(django_request, public_key)


def test_can_verify_django_request_failure(factories, fake_request, now):
    private_key, public_key = keys.get_key_pair()
    signed_request = factories["federation.SignedRequest"](
        auth__key=private_key, auth__headers=["date"]
    )
    prepared = signed_request.prepare()
    django_request = fake_request.get(
        "/",
        **{
            "HTTP_DATE": http_date((now + datetime.timedelta(seconds=31)).timestamp()),
            "HTTP_SIGNATURE": prepared.headers["signature"],
        }
    )
    with pytest.raises(forms.ValidationError):
        signing.verify_django(django_request, public_key)
