"""Tests for the 6 new RIMS features:
  1. ReviewCriterion — structured weighted criteria per call
  2. Review.accepted — reviewer acceptance gate
  3. Psychometric score on Application (scholarships)
  4. HomeValidationRecord (scholarships)
  5. Challenge prize tiers on GrantCall
  6. Milestone actual_completion_date + progress_note
"""
from __future__ import annotations

import datetime
import uuid
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,
    HomeValidationRecord,
    Review,
    ReviewCriterion,
)

User = get_user_model()


# ── Shared fixtures ──────────────────────────────────────────────────────────

@pytest.fixture
def gm(db):
    u = User.objects.create_user(email="gm.nf@test.local", password="pw")
    u.role = UserRole.GRANTS_MANAGER
    u.save()
    return u


@pytest.fixture
def reviewer(db):
    u = User.objects.create_user(email="rev.nf@test.local", password="pw")
    u.role = UserRole.REVIEWER
    u.save()
    return u


@pytest.fixture
def applicant(db):
    u = User.objects.create_user(email="app.nf@test.local", password="pw")
    u.role = UserRole.APPLICANT
    u.save()
    return u


def _call(call_type=GrantCall.CallType.GRANT, **kwargs):
    return GrantCall.objects.create(
        title="Test Call",
        slug=f"test-call-{uuid.uuid4().hex[:8]}",
        call_type=call_type,
        opens_at=timezone.now(),
        closes_at=timezone.now() + datetime.timedelta(days=30),
        status=GrantCall.Status.PUBLISHED,
        **kwargs,
    )


# ── 1. ReviewCriterion ───────────────────────────────────────────────────────

@pytest.mark.django_db
def test_review_criterion_creation():
    call = _call()
    c = ReviewCriterion.objects.create(
        call=call,
        name="Research Quality",
        max_score=10,
        weight=Decimal("40"),
    )
    assert c.scoring_method == ReviewCriterion.ScoringMethod.NUMERIC
    assert str(c) == f"{call.slug} — Research Quality"


@pytest.mark.django_db
def test_review_criteria_weight_sum():
    call = _call()
    ReviewCriterion.objects.create(call=call, name="A", weight=Decimal("60"), max_score=10)
    ReviewCriterion.objects.create(call=call, name="B", weight=Decimal("40"), max_score=10)
    total = sum(c.weight for c in call.review_criteria.all())
    assert total == 100


@pytest.mark.django_db
def test_pass_fail_criterion():
    call = _call()
    c = ReviewCriterion.objects.create(
        call=call,
        name="Eligibility gate",
        scoring_method=ReviewCriterion.ScoringMethod.PASS_FAIL,
        weight=Decimal("0"),
        max_score=1,
    )
    assert c.scoring_method == "pass_fail"
    assert c.get_scoring_method_display() == "Pass / Fail"


# ── 2. Reviewer acceptance gate ──────────────────────────────────────────────

@pytest.mark.django_db
def test_review_accepted_defaults_to_none(applicant, reviewer, gm):
    call = _call()
    app = Application.objects.create(call=call, applicant=applicant)
    review = Review.objects.create(application=app, reviewer=reviewer, assigned_by=gm)
    assert review.accepted is None
    assert review.accepted_at is None


@pytest.mark.django_db
def test_review_acceptance(applicant, reviewer, gm):
    call = _call()
    app = Application.objects.create(call=call, applicant=applicant)
    review = Review.objects.create(application=app, reviewer=reviewer, assigned_by=gm)

    review.accepted = True
    review.accepted_at = timezone.now()
    review.save(update_fields=["accepted", "accepted_at"])
    review.refresh_from_db()

    assert review.accepted is True
    assert review.accepted_at is not None


@pytest.mark.django_db
def test_review_decline(applicant, reviewer, gm):
    call = _call()
    app = Application.objects.create(call=call, applicant=applicant)
    review = Review.objects.create(application=app, reviewer=reviewer, assigned_by=gm)

    review.accepted = False
    review.accepted_at = timezone.now()
    review.save(update_fields=["accepted", "accepted_at"])
    review.refresh_from_db()

    assert review.accepted is False


# ── 3. Psychometric score ────────────────────────────────────────────────────

@pytest.mark.django_db
def test_psychometric_score_defaults_null(applicant):
    call = _call(call_type=GrantCall.CallType.SCHOLARSHIP)
    app = Application.objects.create(call=call, applicant=applicant)
    assert app.psychometric_score is None
    assert app.psychometric_recorded_by is None
    assert app.psychometric_recorded_at is None


@pytest.mark.django_db
def test_psychometric_score_recorded(applicant, gm):
    call = _call(call_type=GrantCall.CallType.SCHOLARSHIP)
    app = Application.objects.create(call=call, applicant=applicant)

    app.psychometric_score = Decimal("72.5")
    app.psychometric_recorded_by = gm
    app.psychometric_recorded_at = timezone.now()
    app.save(update_fields=[
        "psychometric_score", "psychometric_recorded_by", "psychometric_recorded_at"
    ])
    # Re-query to avoid FSMField protected-field refresh issues
    saved = Application.objects.get(pk=app.pk)
    assert float(saved.psychometric_score) == 72.5
    assert saved.psychometric_recorded_by_id == gm.pk


# ── 4. Home validation record ────────────────────────────────────────────────

@pytest.mark.django_db
def test_home_validation_record_creation(applicant, gm):
    call = _call(call_type=GrantCall.CallType.SCHOLARSHIP)
    app = Application.objects.create(call=call, applicant=applicant)

    record = HomeValidationRecord.objects.create(
        application=app,
        conducted_by=gm,
        visit_date=datetime.date.today(),
        key_findings="Single parent, three dependants, thatched roof.",
        recommendation=HomeValidationRecord.Recommendation.PASS,
    )
    assert record.recommendation == "pass"
    assert str(record) == f"Home validation: {app}"
    # Related name works
    assert app.home_validation == record


@pytest.mark.django_db
def test_home_validation_one_to_one(applicant, gm):
    call = _call(call_type=GrantCall.CallType.SCHOLARSHIP)
    app = Application.objects.create(call=call, applicant=applicant)
    HomeValidationRecord.objects.create(
        application=app,
        conducted_by=gm,
        visit_date=datetime.date.today(),
        key_findings="First record.",
        recommendation=HomeValidationRecord.Recommendation.PASS,
    )
    with pytest.raises(Exception):
        HomeValidationRecord.objects.create(
            application=app,
            conducted_by=gm,
            visit_date=datetime.date.today(),
            key_findings="Duplicate.",
            recommendation=HomeValidationRecord.Recommendation.FAIL,
        )


@pytest.mark.django_db
def test_home_validation_borderline(applicant, gm):
    call = _call(call_type=GrantCall.CallType.SCHOLARSHIP)
    app = Application.objects.create(call=call, applicant=applicant)
    record = HomeValidationRecord.objects.create(
        application=app,
        conducted_by=gm,
        visit_date=datetime.date.today(),
        household_income_estimate="Below USD 200/month",
        number_of_dependants=4,
        housing_condition="Mud brick, iron sheet roof",
        key_findings="Family struggles but applicant shows strong determination.",
        recommendation=HomeValidationRecord.Recommendation.BORDERLINE,
    )
    assert record.recommendation == "borderline"
    assert record.number_of_dependants == 4


# ── 5. Challenge prize tiers ─────────────────────────────────────────────────

@pytest.mark.django_db
def test_challenge_prize_tiers():
    call = _call(
        call_type=GrantCall.CallType.CHALLENGE,
        challenge_prize_tiers=[
            {"place": "1st", "label": "Winner", "amount": 5000, "currency": "USD"},
            {"place": "2nd", "label": "Runner-up", "amount": 2500, "currency": "USD"},
            {"place": "3rd", "label": "Merit", "amount": 1000, "currency": "USD"},
        ],
    )
    assert len(call.challenge_prize_tiers) == 3
    assert call.challenge_prize_tiers[0]["place"] == "1st"
    assert call.challenge_prize_tiers[1]["amount"] == 2500
    assert call.challenge_prize_tiers[2]["label"] == "Merit"


@pytest.mark.django_db
def test_challenge_prize_tiers_default_empty():
    call = _call(call_type=GrantCall.CallType.CHALLENGE)
    assert call.challenge_prize_tiers == []


# ── 6. Milestone progress fields ─────────────────────────────────────────────

@pytest.mark.django_db
def test_milestone_progress_note(applicant, gm):
    from apps.rims.projects.models import Milestone, Project
    from apps.rims.grants.models import Award

    call = _call()
    app = Application.objects.create(call=call, applicant=applicant)
    award = Award.objects.create(
        application=app,
        amount=Decimal("10000"),
        currency="USD",
        project_end_date=datetime.date.today() + datetime.timedelta(days=365),
    )
    project = Project.objects.create(
        award=award,
        title="Test project",
        start_date=datetime.date.today(),
        end_date=datetime.date.today() + datetime.timedelta(days=365),
    )
    milestone = Milestone.objects.create(
        project=project,
        name="Phase 1 — Literature review",
        due_date=datetime.date.today() + datetime.timedelta(days=90),
        completion_percent=75,
        progress_note="Literature review completed. Field surveys 60% done.",
    )
    assert milestone.completion_percent == 75
    assert "Literature review" in milestone.progress_note
    assert milestone.actual_completion_date is None


@pytest.mark.django_db
def test_milestone_actual_completion(applicant, gm):
    from apps.rims.projects.models import Milestone, Project
    from apps.rims.grants.models import Award

    call = _call()
    app = Application.objects.create(call=call, applicant=applicant)
    award = Award.objects.create(
        application=app,
        amount=Decimal("10000"),
        currency="USD",
        project_end_date=datetime.date.today() + datetime.timedelta(days=365),
    )
    project = Project.objects.create(
        award=award,
        title="Test project",
        start_date=datetime.date.today(),
        end_date=datetime.date.today() + datetime.timedelta(days=365),
    )
    milestone = Milestone.objects.create(
        project=project,
        name="Phase 2 — Data collection",
        due_date=datetime.date.today() + datetime.timedelta(days=180),
        completion_percent=0,
    )

    today = datetime.date.today()
    milestone.actual_completion_date = today
    milestone.completion_percent = 100
    milestone.progress_note = "All data collected from 5 districts."
    milestone.save(update_fields=["actual_completion_date", "completion_percent", "progress_note"])
    milestone.refresh_from_db()

    assert milestone.completion_percent == 100
    assert milestone.actual_completion_date == today
    assert "5 districts" in milestone.progress_note
