"""Degree-level eligibility is collected during the application itself.

Regression for the bug where a call's ``degree`` eligibility rule screened
against ``UserProfile.degree_level`` — a field the application flow never asked
for — so every applicant was marked "Not eligible" with "Your record: —".

The fix: ask for the degree level during the application (funding form +
scholarship wizard Step 2), persist it to the applicant's profile, and stop the
pre-application gate from locking people out before they can supply it.
"""

from __future__ import annotations

from datetime import timedelta

import pytest
from django.urls import reverse
from django.utils import timezone

from apps.core.authentication.models import UserProfile
from apps.rims.grants.forms import ApplicationForm, ScholarshipStep2Form
from apps.rims.grants.models import Application, EligibilityRule, GrantCall
from apps.rims.grants.services import (
    auto_screen_eligibility,
    degree_rule_levels,
    evaluate_eligibility_for_user,
    submit_application,
)


def _call(call_type="grant", **overrides) -> GrantCall:
    base = {
        "title": "Degree call",
        "slug": f"degree-call-{call_type}",
        "call_type": call_type,
        "opens_at": timezone.now() - timedelta(days=1),
        "closes_at": timezone.now() + timedelta(days=14),
        "status": GrantCall.Status.PUBLISHED,
    }
    base.update(overrides)
    return GrantCall.objects.create(**base)


def _degree_rule(call, levels=("undergraduate", "masters", "phd")):
    return EligibilityRule.objects.create(
        call=call,
        rule_type=EligibilityRule.RuleType.DEGREE,
        params={"levels": list(levels)},
        is_blocking=True,
    )


def _set_degree(user, level):
    profile, _ = UserProfile.objects.get_or_create(user=user)
    profile.degree_level = level
    profile.save()
    return profile


# ── helper ────────────────────────────────────────────────────────────────

@pytest.mark.django_db
def test_degree_rule_levels_reads_configured_levels():
    call = _call(slug="dr-levels")
    assert degree_rule_levels(call) == []
    _degree_rule(call, levels=["masters", "phd"])
    assert degree_rule_levels(call) == ["masters", "phd"]


# ── forms surface the field only when the call screens on degree ────────────

@pytest.mark.django_db
def test_application_form_requires_degree_when_call_has_rule():
    call = _call(slug="app-form-rule")
    _degree_rule(call)
    form = ApplicationForm(call=call)
    assert "degree_level" in form.fields
    assert form.fields["degree_level"].required is True


@pytest.mark.django_db
def test_application_form_drops_degree_without_rule():
    call = _call(slug="app-form-no-rule")
    form = ApplicationForm(call=call)
    assert "degree_level" not in form.fields


@pytest.mark.django_db
def test_scholarship_step2_form_requires_degree_when_call_has_rule():
    call = _call(call_type="scholarship", slug="s2-rule")
    _degree_rule(call)
    assert ScholarshipStep2Form(call=call).fields["degree_level"].required is True
    assert "degree_level" not in ScholarshipStep2Form(call=_call(slug="s2-no-rule")).fields


# ── pre-application gate no longer blocks on MISSING degree ─────────────────

@pytest.mark.django_db
def test_pregate_missing_degree_does_not_block(applicant_user):
    """The whole bug: an un-collected degree must not lock the applicant out."""
    call = _call(slug="pregate-missing")
    _degree_rule(call, levels=["masters", "phd"])
    UserProfile.objects.filter(user=applicant_user).update(degree_level="")
    result = evaluate_eligibility_for_user(applicant_user, call)
    assert result["eligible"] is True


@pytest.mark.django_db
def test_pregate_disallowed_degree_still_blocks(applicant_user):
    call = _call(slug="pregate-disallowed")
    _degree_rule(call, levels=["phd"])
    _set_degree(applicant_user, "masters")
    assert evaluate_eligibility_for_user(applicant_user, call)["eligible"] is False


@pytest.mark.django_db
def test_pregate_allowed_degree_passes(applicant_user):
    call = _call(slug="pregate-allowed")
    _degree_rule(call, levels=["masters", "phd"])
    _set_degree(applicant_user, "masters")
    assert evaluate_eligibility_for_user(applicant_user, call)["eligible"] is True


# ── post-submit screening uses the collected value ─────────────────────────

@pytest.mark.django_db
def test_submit_eligible_when_collected_degree_allowed(applicant_user, institution):
    call = _call(slug="submit-allowed")
    _degree_rule(call, levels=["masters", "phd"])
    _set_degree(applicant_user, "masters")  # what the form now persists
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    submit_application(app)
    app.refresh_from_db()
    assert app.status != Application.Status.INELIGIBLE
    assert app.eligibility_passed is True


@pytest.mark.django_db
def test_submit_ineligible_when_collected_degree_disallowed(applicant_user, institution):
    call = _call(slug="submit-disallowed")
    _degree_rule(call, levels=["phd"])
    _set_degree(applicant_user, "masters")
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    submit_application(app)
    app.refresh_from_db()
    assert app.status == Application.Status.INELIGIBLE


# ── scholarship Step 2 view persists the answer to UserProfile ─────────────

@pytest.mark.django_db
def test_scholarship_step2_post_persists_degree_to_userprofile(client, applicant_user, institution):
    call = _call(call_type="scholarship", slug="s2-view-persist")
    _degree_rule(call, levels=["masters", "phd"])
    _set_degree(applicant_user, "")  # starts empty
    app = Application.objects.create(
        call=call, applicant=applicant_user, institution=institution,
        status=Application.Status.DRAFT,
    )
    client.force_login(applicant_user)
    url = reverse("rims_grants:scholarship_apply_step2", kwargs={"slug": call.slug, "pk": app.pk})
    resp = client.post(url, data={"degree_level": "masters"})
    assert resp.status_code in (301, 302)  # advances to step 3
    applicant_user.profile.refresh_from_db()
    assert applicant_user.profile.degree_level == "masters"
    # And now the application screens as eligible on its degree rule.
    auto_screen_eligibility(app)
    app.refresh_from_db()
    assert app.eligibility_passed is True
