import pyotp
import pytest
from django.urls import reverse

from apps.core.authentication.models import MFADevice
from apps.core.authentication.views import SESSION_MFA_ENROLL_SECRET


@pytest.mark.django_db
def test_login_redirects_to_mfa_when_totp_device_exists(client, django_user_model):
    secret = pyotp.random_base32()
    user = django_user_model.objects.create_user(
        email="mfa@example.com", password="good-password", email_verified=True
    )
    MFADevice.objects.create(
        user=user,
        device_type=MFADevice.DeviceType.TOTP,
        secret=secret,
        is_verified=True,
        name="Test",
    )
    url = reverse("accounts:login")
    resp = client.post(url, {"email": "mfa@example.com", "password": "good-password"})
    assert resp.status_code == 302
    assert resp.url == reverse("accounts:mfa_verify")
    assert client.session.get("mfa_user_id") == user.pk


@pytest.mark.django_db
def test_mfa_verify_success(client, django_user_model):
    secret = pyotp.random_base32()
    user = django_user_model.objects.create_user(email="mfa2@example.com", password="pw")
    MFADevice.objects.create(
        user=user,
        device_type=MFADevice.DeviceType.TOTP,
        secret=secret,
        is_verified=True,
    )
    client.get(reverse("accounts:login"))
    session = client.session
    session["mfa_user_id"] = user.pk
    session.save()

    code = pyotp.TOTP(secret).now()
    url = reverse("accounts:mfa_verify")
    resp = client.post(url, {"code": code})
    assert resp.status_code == 302
    assert resp.url == "/dashboard/"
    assert int(client.session.get("_auth_user_id")) == user.pk


@pytest.mark.django_db
def test_mfa_verify_rejects_bad_code(client, django_user_model):
    secret = pyotp.random_base32()
    user = django_user_model.objects.create_user(email="mfa3@example.com", password="pw")
    MFADevice.objects.create(
        user=user,
        device_type=MFADevice.DeviceType.TOTP,
        secret=secret,
        is_verified=True,
    )
    client.get(reverse("accounts:login"))
    session = client.session
    session["mfa_user_id"] = user.pk
    session.save()
    url = reverse("accounts:mfa_verify")
    # Code from a different secret is virtually never valid for `secret`.
    wrong_code = pyotp.TOTP(pyotp.random_base32()).now()
    resp = client.post(url, {"code": wrong_code})
    assert resp.status_code == 200
    assert b"Invalid code" in resp.content


@pytest.mark.django_db
def test_mfa_enroll_creates_verified_device(client, django_user_model):
    user = django_user_model.objects.create_user(email="enroll@example.com", password="secret123")
    client.force_login(user)

    url = reverse("accounts:mfa_enroll")
    client.get(url)
    secret = client.session[SESSION_MFA_ENROLL_SECRET]
    code = pyotp.TOTP(secret).now()

    resp = client.post(
        url,
        {"code": code, "device_name": "pytest"},
    )
    assert resp.status_code == 302
    assert resp.url == reverse("accounts:mfa_manage")
    assert MFADevice.objects.filter(user=user, is_verified=True, name="pytest").exists()


@pytest.mark.django_db
def test_mfa_remove_requires_password(client, django_user_model):
    secret = pyotp.random_base32()
    user = django_user_model.objects.create_user(email="rm@example.com", password="correct-horse")
    dev = MFADevice.objects.create(
        user=user,
        device_type=MFADevice.DeviceType.TOTP,
        secret=secret,
        is_verified=True,
    )
    client.force_login(user)

    url = reverse("accounts:mfa_remove", kwargs={"pk": dev.pk})
    resp = client.post(url, {"password": "wrong"})
    assert resp.status_code == 302
    assert MFADevice.objects.filter(pk=dev.pk).exists()

    resp = client.post(url, {"password": "correct-horse"})
    assert resp.status_code == 302
    assert not MFADevice.objects.filter(pk=dev.pk).exists()
