"""PRD §4.3.4 + FRFA-AA019 — projects M&E feed, beneficiary model, and the
expenditure widget on the project detail page."""

from __future__ import annotations

from datetime import date, timedelta
from decimal import Decimal
from unittest.mock import patch

import pytest
from django.utils import timezone

from apps.mel.tracking.models import UpstreamEvent
from apps.rims.finance.models import Budget, BudgetLine, DisbursementRequest
from apps.rims.grants.models import Application, Award, GrantCall
from apps.rims.projects.models import Beneficiary, Milestone, Project


@pytest.fixture(autouse=True)
def _no_async_dispatch():
    with patch(
        "apps.core.notifications.services.dispatch_notification_channel.delay"
    ) as fake:
        yield fake


@pytest.fixture
def project(applicant_user, institution):
    call = GrantCall.objects.create(
        title="MEL call",
        slug="mel-call",
        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)
    award = Award.objects.create(
        application=app,
        amount=Decimal("1000"),
        currency="USD",
        project_end_date=date.today() + timedelta(days=180),
        status=Award.Status.ACTIVE,
    )
    return Project.objects.create(
        award=award,
        title="MEL project",
        start_date=date.today() - timedelta(days=10),
        end_date=date.today() + timedelta(days=170),
    )


# ---------------------------------------------------------------------------
# 7.1 — milestone transition feeds the M&EL pipeline
# ---------------------------------------------------------------------------
@pytest.mark.django_db
def test_milestone_accepted_logs_upstream_event(project):
    ms = Milestone.objects.create(
        project=project,
        name="First deliverable",
        due_date=date.today() + timedelta(days=30),
        status=Milestone.Status.SUBMITTED,
    )
    ms.status = Milestone.Status.ACCEPTED
    ms.save()
    assert UpstreamEvent.objects.filter(
        module=UpstreamEvent.Module.RIMS,
        event_type="milestone_accepted",
        source_object_id=ms.pk,
    ).exists()


@pytest.mark.django_db
def test_milestone_overdue_logs_upstream_event(project):
    ms = Milestone.objects.create(
        project=project,
        name="Overdue deliverable",
        due_date=date.today() - timedelta(days=5),
        status=Milestone.Status.PLANNED,
    )
    ms.status = Milestone.Status.OVERDUE
    ms.save()
    assert UpstreamEvent.objects.filter(
        module=UpstreamEvent.Module.RIMS,
        event_type="milestone_overdue",
        source_object_id=ms.pk,
    ).exists()


@pytest.mark.django_db
def test_no_event_when_status_did_not_change(project):
    ms = Milestone.objects.create(
        project=project,
        name="Stable",
        due_date=date.today() + timedelta(days=30),
        status=Milestone.Status.ACCEPTED,
    )
    UpstreamEvent.objects.filter(source_object_id=ms.pk).delete()
    # Re-save without changing status — no new event.
    ms.review_comment = "Still good"
    ms.save()
    assert not UpstreamEvent.objects.filter(
        source_object_id=ms.pk,
        event_type="milestone_accepted",
    ).exists()


# ---------------------------------------------------------------------------
# 7.2 — Beneficiary model
# ---------------------------------------------------------------------------
@pytest.mark.django_db
def test_beneficiary_create_and_demographic_blob(project):
    b = Beneficiary.objects.create(
        project=project,
        full_name="Asha N",
        role=Beneficiary.BeneficiaryRole.DIRECT,
        demographic={"gender": "F", "age": 24, "district": "Mukono"},
    )
    assert b.demographic["district"] == "Mukono"
    assert project.beneficiaries.count() == 1


# ---------------------------------------------------------------------------
# 7.3 — Expenditure widget context on ProjectDetailView
# ---------------------------------------------------------------------------
@pytest.mark.django_db
def test_project_detail_exposes_expenditure_snapshot(project, client, grants_manager_user):
    budget = Budget.objects.create(
        award=project.award,
        name="Default",
        currency="USD",
        status=Budget.Status.APPROVED,
    )
    BudgetLine.objects.create(budget=budget, label="Line", amount=Decimal("500"))
    DisbursementRequest.objects.create(
        budget=budget,
        amount=Decimal("100"),
        status=DisbursementRequest.Status.PAID,
    )
    project.finance_budget = budget
    project.save(update_fields=["finance_budget"])
    client.force_login(grants_manager_user)
    resp = client.get(f"/rims/projects/{project.pk}/")
    assert resp.status_code == 200
    snap = resp.context["expenditure_snapshot"]
    assert snap is not None
    assert snap["budget_ceiling"] == Decimal("500")
    assert snap["total_disbursed"] == Decimal("100")


@pytest.mark.django_db
def test_project_detail_no_widget_without_finance_budget(project, client, grants_manager_user):
    client.force_login(grants_manager_user)
    resp = client.get(f"/rims/projects/{project.pk}/")
    assert resp.status_code == 200
    assert resp.context["expenditure_snapshot"] is None
