"""UI follow-up — view-level smoke tests for ForceAwardCloseoutView and
CallWithdrawView (PRD §5.1 FRFA-CO023, FRFA-CS030).

The underlying services already have logic tests; these only verify that the
views are reachable, role-gated, and call into the right service on POST.
"""

from __future__ import annotations

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

import pytest
from django.contrib.auth import get_user_model
from django.urls import reverse
from django.utils import timezone

from apps.core.permissions.roles import UserRole
from apps.rims.grants.models import (
    Application,
    Award,
    GrantCall,
)

User = get_user_model()


@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 program_director(db):
    u = User.objects.create_user(email="pd.ui@example.com", password="x")
    u.role = UserRole.PROGRAM_DIRECTOR
    u.save()
    return u


# ---------------------------------------------------------------------------
# ForceAwardCloseoutView
# ---------------------------------------------------------------------------
@pytest.mark.django_db
def test_force_closeout_view_requires_pd_role(
    client, applicant_user, institution, grants_manager_user
):
    call = GrantCall.objects.create(
        title="Force ui",
        slug="force-ui",
        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),
    )
    # Grants Manager cannot reach the view.
    client.force_login(grants_manager_user)
    resp = client.get(reverse("rims_grants:award_force_closeout", kwargs={"pk": award.pk}))
    assert resp.status_code in (302, 403)


@pytest.mark.django_db
def test_force_closeout_view_get_renders_for_pd(
    client, applicant_user, institution, program_director
):
    call = GrantCall.objects.create(
        title="Force ui pd",
        slug="force-ui-pd",
        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),
    )
    client.force_login(program_director)
    resp = client.get(reverse("rims_grants:award_force_closeout", kwargs={"pk": award.pk}))
    assert resp.status_code == 200
    assert b"Force close-out" in resp.content


@pytest.mark.django_db
def test_force_closeout_view_post_rejects_empty_justification(
    client, applicant_user, institution, program_director
):
    call = GrantCall.objects.create(
        title="Force empty",
        slug="force-empty",
        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),
    )
    client.force_login(program_director)
    resp = client.post(
        reverse("rims_grants:award_force_closeout", kwargs={"pk": award.pk}),
        {"justification": "   "},
    )
    assert resp.status_code == 400
    award = Award.objects.get(pk=award.pk)
    assert award.status != Award.Status.CLOSED


@pytest.mark.django_db
def test_force_closeout_view_post_with_justification_closes_award(
    client, applicant_user, institution, program_director
):
    call = GrantCall.objects.create(
        title="Force ok",
        slug="force-ok",
        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),
    )
    client.force_login(program_director)
    resp = client.post(
        reverse("rims_grants:award_force_closeout", kwargs={"pk": award.pk}),
        {"justification": "Awardee unresponsive past 90-day grace; final reports outstanding."},
    )
    assert resp.status_code == 302
    award = Award.objects.get(pk=award.pk)
    assert award.status == Award.Status.CLOSED


# ---------------------------------------------------------------------------
# CallWithdrawView
# ---------------------------------------------------------------------------
@pytest.mark.django_db
def test_call_withdraw_view_get_renders(client, grants_manager_user):
    call = GrantCall.objects.create(
        title="Withdraw ui",
        slug="withdraw-ui",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=14),
        status=GrantCall.Status.PUBLISHED,
    )
    client.force_login(grants_manager_user)
    resp = client.get(reverse("rims_grants:call_withdraw", kwargs={"slug": call.slug}))
    assert resp.status_code == 200
    assert b"Withdraw call" in resp.content


@pytest.mark.django_db
def test_call_withdraw_view_post_with_reason_flips_status(
    client, grants_manager_user
):
    call = GrantCall.objects.create(
        title="Withdraw ok",
        slug="withdraw-ok",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=14),
        status=GrantCall.Status.PUBLISHED,
    )
    client.force_login(grants_manager_user)
    resp = client.post(
        reverse("rims_grants:call_withdraw", kwargs={"slug": call.slug}),
        {"reason": "Donor paused funding pending board review."},
    )
    assert resp.status_code == 302
    call = GrantCall.objects.get(pk=call.pk)
    assert call.status == GrantCall.Status.WITHDRAWN
    assert "Donor paused" in call.withdrawal_reason


@pytest.mark.django_db
def test_call_withdraw_view_post_rejects_empty_reason(client, grants_manager_user):
    call = GrantCall.objects.create(
        title="Withdraw empty",
        slug="withdraw-empty",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=14),
        status=GrantCall.Status.PUBLISHED,
    )
    client.force_login(grants_manager_user)
    resp = client.post(
        reverse("rims_grants:call_withdraw", kwargs={"slug": call.slug}),
        {"reason": ""},
    )
    assert resp.status_code == 400
    call = GrantCall.objects.get(pk=call.pk)
    assert call.status == GrantCall.Status.PUBLISHED
