"""P0 (RIMS assessment §3 #7, #8) — PaymentBatch reversal + DisbursementRequest resubmit.

Coverage:
- reverse_payment_batch: requires reason; refuses non-EXECUTED batches; walks
  every child execution → REVERSED, request → APPROVED; writes AuditLog rows.
- resubmit_disbursement_request: refuses non-REJECTED requests; flips back to
  DRAFT; clears rejection_reason + approved_*; writes a DISBURSEMENT_RESUBMIT
  audit row.
"""
from __future__ import annotations

from datetime import date, timedelta
from decimal import Decimal

import pytest
from django.core.exceptions import ValidationError
from django.urls import reverse
from django.utils import timezone

from apps.core.audit.models import AuditLog
from apps.rims.finance.models import (
    Budget,
    BudgetLine,
    DisbursementExecution,
    DisbursementRequest,
    PaymentBatch,
)
from apps.rims.finance.services import (
    create_payment_batch,
    execute_payment_batch,
    lock_payment_batch,
    resubmit_disbursement_request,
    reverse_payment_batch,
)
from apps.rims.grants.models import Application, GrantCall
from apps.rims.grants.services import award_application, shortlist_application, submit_application


def _award(applicant_user, institution):
    call = GrantCall.objects.create(
        title="P0 reversal",
        slug=f"p0-rev-{int(timezone.now().timestamp() * 1000) % 10_000_000}",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=10),
        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)
    shortlist_application(app)
    award_application(app, Decimal("1000"), date.today() + timedelta(days=180), narrative="")
    return Application.objects.get(pk=app.pk).award


def _budget(award):
    return Budget.objects.create(award=award, name="P0 budget", currency=award.currency)


def _approved_request(budget, amount):
    BudgetLine.objects.create(budget=budget, label="L", amount=Decimal("9999"))
    return DisbursementRequest.objects.create(
        budget=budget,
        amount=Decimal(amount),
        status=DisbursementRequest.Status.APPROVED,
        approved_at=timezone.now(),
        payee_email="payee@example.com",
    )


# ---------------------------------------------------------------------------
# PaymentBatch reversal
# ---------------------------------------------------------------------------

@pytest.mark.django_db
def test_reverse_requires_reason(applicant_user, institution):
    award = _award(applicant_user, institution)
    budget = _budget(award)
    req = _approved_request(budget, "100")
    batch = create_payment_batch([req], batch_reference="BATCH-REV-NOREASON")
    lock_payment_batch(batch)
    execute_payment_batch(batch, item_references={i.pk: f"REF-{i.pk}" for i in batch.items.all()})

    with pytest.raises(ValidationError):
        reverse_payment_batch(batch, reason="")


@pytest.mark.django_db
def test_reverse_refuses_non_executed(applicant_user, institution):
    award = _award(applicant_user, institution)
    budget = _budget(award)
    req = _approved_request(budget, "100")
    batch = create_payment_batch([req], batch_reference="BATCH-REV-DRAFT")

    with pytest.raises(ValidationError):
        reverse_payment_batch(batch, reason="invalid state test")


@pytest.mark.django_db
def test_reverse_walks_children_back_to_approved(applicant_user, institution):
    award = _award(applicant_user, institution)
    budget = _budget(award)
    r1 = _approved_request(budget, "100")
    r2 = _approved_request(budget, "250")
    batch = create_payment_batch([r1, r2], batch_reference="BATCH-REV-OK")
    lock_payment_batch(batch)
    execute_payment_batch(batch, item_references={i.pk: f"REF-{i.pk}" for i in batch.items.all()})
    # Confirm baseline: requests are PAID.
    r1.refresh_from_db(); r2.refresh_from_db()
    assert r1.status == DisbursementRequest.Status.PAID
    assert r2.status == DisbursementRequest.Status.PAID

    reverse_payment_batch(
        batch,
        reason="NSF chargeback returned by bank statement",
        reference="CHB-2026-05-14-0042",
    )

    batch.refresh_from_db()
    r1.refresh_from_db(); r2.refresh_from_db()
    assert batch.status == PaymentBatch.Status.REVERSED
    assert batch.reversal_reference == "CHB-2026-05-14-0042"
    assert r1.status == DisbursementRequest.Status.APPROVED
    assert r2.status == DisbursementRequest.Status.APPROVED
    for req in (r1, r2):
        execution = DisbursementExecution.objects.get(request=req)
        assert execution.status == DisbursementExecution.Status.REVERSED
        assert execution.reversal_reference == "CHB-2026-05-14-0042"

    # Per-item audit row + one batch-level row.
    item_audits = AuditLog.objects.filter(
        action="DISBURSEMENT_REVERSED",
        target_model="DisbursementRequest",
    )
    assert item_audits.count() == 2
    assert AuditLog.objects.filter(
        action="PAYMENT_BATCH_REVERSED",
        target_model="PaymentBatch",
    ).count() == 1


# ---------------------------------------------------------------------------
# DisbursementRequest resubmit
# ---------------------------------------------------------------------------

@pytest.mark.django_db
def test_resubmit_refuses_non_rejected(applicant_user, institution):
    award = _award(applicant_user, institution)
    budget = _budget(award)
    req = _approved_request(budget, "100")
    with pytest.raises(ValidationError):
        resubmit_disbursement_request(req)


@pytest.mark.django_db
def test_rejected_disbursement_is_editable_via_edit_view(client, applicant_user, institution, finance_user):
    """UI follow-up F1 — DisbursementUpdateView.get_queryset() must accept
    REJECTED so the resubmit modal in disbursement_detail can drive it."""
    award = _award(applicant_user, institution)
    budget = _budget(award)
    req = _approved_request(budget, "100")
    req.status = DisbursementRequest.Status.REJECTED
    req.rejection_reason = "Missing invoice"
    req.save(update_fields=["status", "rejection_reason"])

    client.force_login(finance_user)
    resp = client.get(reverse("rims_finance:disbursement_edit", kwargs={"pk": req.pk}))
    assert resp.status_code == 200


@pytest.mark.django_db
def test_resubmit_walks_back_to_draft_and_clears_metadata(applicant_user, institution, finance_user):
    award = _award(applicant_user, institution)
    budget = _budget(award)
    req = _approved_request(budget, "100")
    req.status = DisbursementRequest.Status.REJECTED
    req.rejection_reason = "Missing invoice"
    req.approved_by = finance_user
    req.approved_at = timezone.now()
    req.save(update_fields=["status", "rejection_reason", "approved_by", "approved_at"])

    resubmit_disbursement_request(
        req,
        actor=finance_user,
        amount=Decimal("150"),
        justification="Now with invoice attached",
    )
    req.refresh_from_db()

    assert req.status == DisbursementRequest.Status.DRAFT
    assert req.rejection_reason == ""
    assert req.approved_by_id is None
    assert req.approved_at is None
    assert req.amount == Decimal("150")
    assert req.justification == "Now with invoice attached"

    audit = AuditLog.objects.filter(
        action="DISBURSEMENT_RESUBMIT",
        target_model="DisbursementRequest",
        object_id=str(req.pk),
    ).first()
    assert audit is not None
    assert "amount" in audit.changes
