import uuid
from datetime import timedelta
from decimal import Decimal

import pytest
from django.utils import timezone

from apps.core.permissions.roles import UserRole
from apps.rims.grants.models import Application, GrantCall, Review
from apps.rims.grants.review_conflicts import recommendation_divergence, resolve_conflict
from apps.rims.grants.services import submit_application, submit_review


@pytest.mark.django_db
def test_recommendation_divergence_true_when_text_differs(
    django_user_model, applicant_user, institution, reviewer_user
):
    r2 = django_user_model.objects.create_user(email="r2@c.test", password="x")
    r2.role = UserRole.REVIEWER
    r2.save()

    call = GrantCall.objects.create(
        title="C",
        slug=f"c-{uuid.uuid4().hex[:8]}",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=20),
        status=GrantCall.Status.PUBLISHED,
    )
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    submit_application(app)
    app = Application.objects.get(pk=app.pk)
    Review.objects.create(application=app, reviewer=reviewer_user)
    Review.objects.create(application=app, reviewer=r2)
    rv1 = Review.objects.get(application=app, reviewer=reviewer_user)
    rv2 = Review.objects.get(application=app, reviewer=r2)
    submit_review(rv1, {"Overall": Decimal("10")}, "Fund this proposal.")
    submit_review(rv2, {"Overall": Decimal("10")}, "Do not fund.")
    divergent, _ = recommendation_divergence(Application.objects.get(pk=app.pk))
    assert divergent is True


@pytest.mark.django_db
def test_recommendation_divergence_false_when_same_text(
    django_user_model, applicant_user, institution, reviewer_user
):
    r2 = django_user_model.objects.create_user(email="r3@c.test", password="x")
    r2.role = UserRole.REVIEWER
    r2.save()
    call = GrantCall.objects.create(
        title="D",
        slug=f"d-{uuid.uuid4().hex[:8]}",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=20),
        status=GrantCall.Status.PUBLISHED,
    )
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    submit_application(app)
    app = Application.objects.get(pk=app.pk)
    Review.objects.create(application=app, reviewer=reviewer_user)
    Review.objects.create(application=app, reviewer=r2)
    rv1 = Review.objects.get(application=app, reviewer=reviewer_user)
    rv2 = Review.objects.get(application=app, reviewer=r2)
    submit_review(rv1, {"Overall": Decimal("5")}, "Recommend funding.")
    submit_review(rv2, {"Overall": Decimal("5")}, "Recommend funding.")
    divergent, _ = recommendation_divergence(Application.objects.get(pk=app.pk))
    assert divergent is False


def _seed_three_reviews(
    call, applicant_user, institution, django_user_model, *, codes, scores=None
):
    """Helper: create 3 submitted reviews with the given recommendation_codes."""
    from apps.core.permissions.roles import UserRole as UR

    if scores is None:
        scores = ["10", "10", "10"]
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    submit_application(app)
    app = Application.objects.get(pk=app.pk)
    reviewers = []
    for i in range(3):
        u = django_user_model.objects.create_user(email=f"rc{i}-{uuid.uuid4().hex[:6]}@ex.com", password="x")
        u.role = UR.REVIEWER
        u.save()
        reviewers.append(u)
    for rev_user, code, sc in zip(reviewers, codes, scores):
        r = Review.objects.create(application=app, reviewer=rev_user, recommendation_code=code)
        submit_review(r, {"Overall": Decimal(sc)}, f"decision: {code}")
    return Application.objects.get(pk=app.pk)


@pytest.mark.django_db
def test_resolve_conflict_majority_policy_picks_winner(
    django_user_model, applicant_user, institution
):
    call = GrantCall.objects.create(
        title="Maj",
        slug=f"maj-{uuid.uuid4().hex[:8]}",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=20),
        status=GrantCall.Status.PUBLISHED,
        conflict_resolution=GrantCall.ConflictResolution.MAJORITY,
    )
    app = _seed_three_reviews(
        call,
        applicant_user,
        institution,
        django_user_model,
        codes=["approve", "approve", "reject"],
    )
    outcome = resolve_conflict(app)
    assert outcome.has_conflict is True
    assert outcome.resolved_code == "approve"
    assert "2/3" in outcome.rationale


@pytest.mark.django_db
def test_resolve_conflict_majority_returns_none_on_tie(
    django_user_model, applicant_user, institution
):
    call = GrantCall.objects.create(
        title="MajTie",
        slug=f"majtie-{uuid.uuid4().hex[:8]}",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=20),
        status=GrantCall.Status.PUBLISHED,
        conflict_resolution=GrantCall.ConflictResolution.MAJORITY,
    )
    app = _seed_three_reviews(
        call,
        applicant_user,
        institution,
        django_user_model,
        codes=["approve", "reject", "revise"],
    )
    outcome = resolve_conflict(app)
    assert outcome.has_conflict is True
    assert outcome.resolved_code is None
    assert "No strict majority" in outcome.rationale


@pytest.mark.django_db
def test_resolve_conflict_average_threshold_resolves_up(
    django_user_model, applicant_user, institution
):
    call = GrantCall.objects.create(
        title="Avg",
        slug=f"avg-{uuid.uuid4().hex[:8]}",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=20),
        status=GrantCall.Status.PUBLISHED,
        conflict_resolution=GrantCall.ConflictResolution.AVERAGE_THRESHOLD,
        review_score_threshold=Decimal("8"),
    )
    app = _seed_three_reviews(
        call,
        applicant_user,
        institution,
        django_user_model,
        codes=["approve", "reject", "approve"],
        scores=["9", "9", "9"],
    )
    outcome = resolve_conflict(app)
    assert outcome.resolved_code == "approve"
    assert "threshold 8" in outcome.rationale


@pytest.mark.django_db
def test_resolve_conflict_escalate_defers_to_manager(
    django_user_model, applicant_user, institution
):
    call = GrantCall.objects.create(
        title="Esc",
        slug=f"esc-{uuid.uuid4().hex[:8]}",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=20),
        status=GrantCall.Status.PUBLISHED,
        conflict_resolution=GrantCall.ConflictResolution.ESCALATE,
    )
    app = _seed_three_reviews(
        call,
        applicant_user,
        institution,
        django_user_model,
        codes=["approve", "reject", "approve"],
    )
    outcome = resolve_conflict(app)
    assert outcome.has_conflict is True
    assert outcome.resolved_code is None
    assert "Escalated" in outcome.rationale


@pytest.mark.django_db
def test_resolve_conflict_returns_no_conflict_when_agreement(
    django_user_model, applicant_user, institution
):
    call = GrantCall.objects.create(
        title="Agree",
        slug=f"agree-{uuid.uuid4().hex[:8]}",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=20),
        status=GrantCall.Status.PUBLISHED,
        conflict_resolution=GrantCall.ConflictResolution.MAJORITY,
    )
    app = _seed_three_reviews(
        call,
        applicant_user,
        institution,
        django_user_model,
        codes=["approve", "approve", "approve"],
    )
    # Same text across reviews means no divergence, regardless of policy.
    for r in app.reviews.all():
        r.recommendation = "Yes"
        r.save(update_fields=["recommendation"])
    outcome = resolve_conflict(Application.objects.get(pk=app.pk))
    assert outcome.has_conflict is False
    assert outcome.resolved_code is None
