"""P0 (RIMS assessment §3 #9) — scholar withdrawal cancels pending stipends.

Coverage:
- Withdrawal cancels every PENDING stipend, leaves PAID rows untouched, and
  writes a STIPEND_CANCELLED audit row per cancellation.
- process_stipend refuses to post against a withdrawn scholar.
- StipendForm refuses to save against a withdrawn scholar.
"""
from __future__ import annotations

from datetime import date
from decimal import Decimal

import pytest
from django.core.exceptions import ValidationError

from apps.core.audit.models import AuditLog
from apps.rims.scholarships.forms import StipendForm
from apps.rims.scholarships.models import Scholar, Scholarship, Stipend
from apps.rims.scholarships.services import process_stipend
from apps.rims.scholarships.workflows import transition_scholar


@pytest.fixture
def active_scholar(db, applicant_user):
    programme = Scholarship.objects.create(name="P0 cascade MSc")
    scholar = Scholar.objects.create(user=applicant_user, scholarship=programme)
    transition_scholar(scholar, to_status=Scholar.Status.ACTIVE)
    scholar.refresh_from_db()
    return scholar


@pytest.mark.django_db
def test_withdrawal_cancels_pending_stipends(active_scholar, grants_manager_user):
    pending = Stipend.objects.create(
        scholar=active_scholar,
        amount=Decimal("500"),
        currency="USD",
        paid_on=date(2026, 6, 1),
        status=Stipend.Status.PENDING,
    )
    paid = Stipend.objects.create(
        scholar=active_scholar,
        amount=Decimal("400"),
        currency="USD",
        paid_on=date(2026, 4, 1),
        status=Stipend.Status.PAID,
    )

    transition_scholar(
        active_scholar,
        to_status=Scholar.Status.WITHDRAWN,
        actor=grants_manager_user,
        reason="Lost contact",
    )

    pending.refresh_from_db()
    paid.refresh_from_db()
    assert pending.status == Stipend.Status.CANCELLED
    assert pending.cancelled_at is not None
    assert pending.cancelled_by == grants_manager_user
    assert "Lost contact" in pending.cancelled_reason
    assert paid.status == Stipend.Status.PAID  # untouched

    assert AuditLog.objects.filter(
        action="STIPEND_CANCELLED",
        target_app="rims_scholarships",
        target_model="Stipend",
        object_id=str(pending.pk),
    ).exists()


@pytest.mark.django_db
def test_process_stipend_refuses_withdrawn_scholar(active_scholar):
    transition_scholar(
        active_scholar,
        to_status=Scholar.Status.WITHDRAWN,
        reason="audit",
    )
    active_scholar.refresh_from_db()
    with pytest.raises(ValidationError):
        process_stipend(
            active_scholar,
            amount=Decimal("100"),
            currency="USD",
            paid_on=date.today(),
        )


@pytest.mark.django_db
def test_stipend_form_blocks_withdrawn_scholar(active_scholar):
    transition_scholar(
        active_scholar,
        to_status=Scholar.Status.WITHDRAWN,
        reason="audit",
    )
    active_scholar.refresh_from_db()
    form = StipendForm(
        data={
            "scholar": active_scholar.pk,
            "amount": "100",
            "currency": "USD",
            "paid_on": date.today().isoformat(),
            "reference": "",
        }
    )
    assert not form.is_valid()
    errors = " ".join(form.errors.get("__all__", []))
    assert "withdrawn" in errors.lower()
