"""PRD §5.1 FRFA-AM026 — aggregated weighted reviewer score panel.

The weighted aggregate must respect each criterion's max_points so
high-stakes criteria dominate the headline number, and must differ from
the simple average when criteria carry uneven weights.
"""

from __future__ import annotations

from datetime import timedelta
from decimal import Decimal

import pytest
from django.utils import timezone

from apps.rims.grants.models import Application, GrantCall, Review
from apps.rims.grants.services import aggregate_weighted_score


def _call_with_rubric(rubric: list[dict]) -> GrantCall:
    return GrantCall.objects.create(
        title="Scoring call",
        slug="scoring-call",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=30),
        status=GrantCall.Status.PUBLISHED,
        scoring_rubric=rubric,
    )


def _submitted_review(application, reviewer, scores: dict) -> Review:
    # JSONField can't serialise Decimal — persist as floats but keep the
    # totals in Decimal so the service-level math is precise.
    json_safe = {k: float(v) for k, v in scores.items()}
    total = sum((Decimal(str(v)) for v in scores.values()), Decimal("0"))
    return Review.objects.create(
        application=application,
        reviewer=reviewer,
        scores=json_safe,
        total_score=total,
        submitted_at=timezone.now(),
    )


@pytest.mark.django_db
def test_returns_none_when_no_submitted_reviews(applicant_user, institution):
    """Empty queue → None so callers can render an empty-state."""
    call = _call_with_rubric([{"criterion": "Overall", "max_points": 10}])
    app = Application.objects.create(
        call=call, applicant=applicant_user, institution=institution
    )
    assert aggregate_weighted_score(app) is None


@pytest.mark.django_db
def test_weighted_score_differs_from_simple_average(
    applicant_user, institution, reviewer_user, grants_manager_user
):
    """A criterion with 3× the max_points must dominate the aggregate."""
    call = _call_with_rubric(
        [
            {"criterion": "Methodology", "max_points": 30},
            {"criterion": "Clarity", "max_points": 10},
        ]
    )
    app = Application.objects.create(
        call=call, applicant=applicant_user, institution=institution
    )
    _submitted_review(
        app, reviewer_user, {"Methodology": Decimal("30"), "Clarity": Decimal("0")}
    )
    _submitted_review(
        app,
        grants_manager_user,
        {"Methodology": Decimal("30"), "Clarity": Decimal("0")},
    )

    summary = aggregate_weighted_score(app)
    assert summary is not None
    # Weighted_pct = (30 + 0) / (30 + 10) * 100 = 75
    assert summary["weighted_pct"] == Decimal("75.00")
    # Raw mean total = 30 (since each reviewer's total is 30)
    assert summary["raw_total_mean"] == Decimal("30.00")
    assert summary["reviewers_counted"] == 2
    # Per-criterion mean for Methodology = 30; Clarity = 0.
    by_label = {row["label"]: row for row in summary["per_criterion"]}
    assert by_label["Methodology"]["mean_score"] == Decimal("30.00")
    assert by_label["Methodology"]["weight"] == Decimal("30")
    assert by_label["Clarity"]["mean_score"] == Decimal("0.00")
    assert by_label["Clarity"]["share_pct"] == Decimal("0.00")


@pytest.mark.django_db
def test_per_criterion_means_average_across_reviewers(
    applicant_user, institution, reviewer_user, grants_manager_user
):
    """Per-criterion mean is the average of submitted reviewers' scores."""
    call = _call_with_rubric([{"criterion": "Overall", "max_points": 10}])
    app = Application.objects.create(
        call=call, applicant=applicant_user, institution=institution
    )
    _submitted_review(app, reviewer_user, {"Overall": Decimal("8")})
    _submitted_review(app, grants_manager_user, {"Overall": Decimal("6")})

    summary = aggregate_weighted_score(app)
    assert summary["per_criterion"][0]["mean_score"] == Decimal("7.00")
    assert summary["weighted_pct"] == Decimal("70.00")


@pytest.mark.django_db
def test_callable_when_rubric_missing(applicant_user, institution, reviewer_user):
    """A call without a configured rubric falls back to a single Overall row."""
    call = GrantCall.objects.create(
        title="No rubric",
        slug="no-rubric",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=14),
        status=GrantCall.Status.PUBLISHED,
    )
    app = Application.objects.create(
        call=call, applicant=applicant_user, institution=institution
    )
    Review.objects.create(
        application=app,
        reviewer=reviewer_user,
        scores={},
        total_score=Decimal("7"),
        submitted_at=timezone.now(),
    )
    summary = aggregate_weighted_score(app)
    assert summary is not None
    assert summary["per_criterion"][0]["label"] == "Overall"
    assert summary["reviewers_counted"] == 1
