"""PRD §5.1 FRFA-AM032 — automated ranked shortlist.

Covers:
- Threshold filter (review_score_threshold)
- max_awards cap
- Tie-break policies: MANAGER_REVIEW, EARLIEST_SUBMISSION, INTERVIEW_SCORE
- Unscored applications are reported separately
- apply_shortlist_result() transitions chosen apps to SHORTLISTED
"""
from datetime import timedelta
from decimal import Decimal

import pytest
from django.contrib.auth import get_user_model
from django.utils import timezone

from apps.core.permissions.roles import UserRole
from apps.rims.grants.models import (
    Application,
    GrantCall,
    InterviewOutcome,
    InterviewSchedule,
    Review,
)
from apps.rims.grants.services import (
    apply_shortlist_result,
    generate_shortlist,
    submit_application,
    submit_review,
)

User = get_user_model()


def _reviewer(db, email="rev-gen@example.com"):
    u = User.objects.create_user(email=email, password="pw")
    u.role = UserRole.REVIEWER
    u.save()
    return u


def _score_application(app, reviewer, *, total):
    """Create and submit a review with rubric ``{"Merit": total}`` so the
    existing aggregate_average_score helper returns *total* for the app."""
    review = Review.objects.create(application=app, reviewer=reviewer)
    submit_review(review, {"Merit": Decimal(str(total))}, "ok")


def _make_call(**overrides):
    base = dict(
        title="Shortlist call",
        slug=f"sl-{int(timezone.now().timestamp() * 1000000) % 10_000_000}",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=10),
        status=GrantCall.Status.PUBLISHED,
        max_awards=2,
        tie_break_policy=GrantCall.TieBreakPolicy.MANAGER_REVIEW,
    )
    base.update(overrides)
    return GrantCall.objects.create(**base)


def _submit(applicant, call, institution):
    app = Application.objects.create(call=call, applicant=applicant, institution=institution)
    submit_application(app)
    return Application.objects.get(pk=app.pk)


@pytest.fixture
def three_applicants(db):
    def make(email, first):
        u = User.objects.create_user(email=email, password="pw")
        u.first_name = first
        u.save()
        return u

    return [
        make("a@example.com", "Ava"),
        make("b@example.com", "Bea"),
        make("c@example.com", "Cal"),
    ]


@pytest.mark.django_db
def test_threshold_filters_below_threshold(three_applicants, institution):
    call = _make_call(review_score_threshold=Decimal("7"), max_awards=3)
    rev = _reviewer(None)
    apps = [_submit(u, call, institution) for u in three_applicants]
    _score_application(apps[0], rev, total="9")
    _score_application(apps[1], rev, total="5")  # below threshold
    _score_application(apps[2], rev, total="8")

    result = generate_shortlist(call)
    chosen_ids = {a.pk for a in result.chosen}
    assert chosen_ids == {apps[0].pk, apps[2].pk}
    assert [a.pk for a in result.below_threshold] == [apps[1].pk]


@pytest.mark.django_db
def test_max_awards_cap(three_applicants, institution):
    call = _make_call(max_awards=2)
    rev = _reviewer(None)
    apps = [_submit(u, call, institution) for u in three_applicants]
    _score_application(apps[0], rev, total="9")
    _score_application(apps[1], rev, total="8")
    _score_application(apps[2], rev, total="7")

    result = generate_shortlist(call)
    assert len(result.chosen) == 2
    assert {a.pk for a in result.chosen} == {apps[0].pk, apps[1].pk}
    assert [a.pk for a in result.tied] == [apps[2].pk]


@pytest.mark.django_db
def test_tie_break_manager_review_flags_all_tied(three_applicants, institution):
    call = _make_call(
        max_awards=1, tie_break_policy=GrantCall.TieBreakPolicy.MANAGER_REVIEW
    )
    rev = _reviewer(None)
    apps = [_submit(u, call, institution) for u in three_applicants[:2]]
    _score_application(apps[0], rev, total="9")
    _score_application(apps[1], rev, total="9")

    result = generate_shortlist(call)
    # Manager must decide: neither is auto-chosen.
    assert result.chosen == []
    assert {a.pk for a in result.tied} == {apps[0].pk, apps[1].pk}


@pytest.mark.django_db
def test_tie_break_earliest_submission(three_applicants, institution):
    call = _make_call(
        max_awards=1, tie_break_policy=GrantCall.TieBreakPolicy.EARLIEST_SUBMISSION
    )
    rev = _reviewer(None)
    apps = [_submit(u, call, institution) for u in three_applicants[:2]]
    # Manually stagger submitted_at so there is an unambiguous earlier one.
    t1 = timezone.now() - timedelta(hours=3)
    t2 = timezone.now() - timedelta(hours=1)
    Application.objects.filter(pk=apps[0].pk).update(submitted_at=t2)
    Application.objects.filter(pk=apps[1].pk).update(submitted_at=t1)
    _score_application(apps[0], rev, total="9")
    _score_application(apps[1], rev, total="9")

    result = generate_shortlist(call)
    assert [a.pk for a in result.chosen] == [apps[1].pk]
    assert [a.pk for a in result.tied] == [apps[0].pk]


@pytest.mark.django_db
def test_unscored_applications_are_reported(three_applicants, institution):
    call = _make_call(max_awards=5)
    rev = _reviewer(None)
    apps = [_submit(u, call, institution) for u in three_applicants]
    _score_application(apps[0], rev, total="8")
    # apps[1] and apps[2] have no reviews.

    result = generate_shortlist(call)
    assert [a.pk for a in result.chosen] == [apps[0].pk]
    assert {a.pk for a in result.unscored} == {apps[1].pk, apps[2].pk}


@pytest.mark.django_db
def test_apply_shortlist_result_transitions_chosen_to_shortlisted(
    three_applicants, institution
):
    call = _make_call(max_awards=2)
    rev = _reviewer(None)
    apps = [_submit(u, call, institution) for u in three_applicants[:2]]
    _score_application(apps[0], rev, total="9")
    _score_application(apps[1], rev, total="8")

    result = generate_shortlist(call)
    assert len(result.chosen) == 2
    applied = apply_shortlist_result(call, result)
    assert applied == 2
    for app in apps:
        assert Application.objects.get(pk=app.pk).status == Application.Status.SHORTLISTED
