"""PRD §5.1 FRFA-AA021–023 — award amendment approval FSM + version log.

Covers:
- REQUESTED → APPROVED transition (approve_award_amendment)
- REQUESTED → REJECTED transition with mandatory reason
- Approved amendment applies new_terms to the Award and preserves
  previous_terms as a versioned snapshot
- Once decided, an amendment cannot be re-decided (terminal states)
"""
from datetime import date, timedelta
from decimal import Decimal

import pytest
from django.core.exceptions import ValidationError
from django.utils import timezone
from django_fsm import TransitionNotAllowed

from apps.rims.grants.models import Application, AwardAmendment, GrantCall
from apps.rims.grants.services import (
    approve_award_amendment,
    award_application,
    create_award_amendment,
    reject_award_amendment,
    shortlist_application,
    submit_application,
)


def _award_for(applicant_user, institution, *, suffix="am"):
    call = GrantCall.objects.create(
        title="Amendment call",
        slug=f"am-{suffix}-{int(timezone.now().timestamp())}",
        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


@pytest.mark.django_db
def test_create_amendment_captures_previous_terms(applicant_user, institution, grants_manager_user):
    award = _award_for(applicant_user, institution, suffix="capture")
    amendment = create_award_amendment(
        award,
        change_summary="Extend project end",
        justification="Delay due to data access",
        new_terms={"project_end_date": (date.today() + timedelta(days=365)).isoformat()},
        actor=grants_manager_user,
    )
    assert amendment.status == AwardAmendment.Status.REQUESTED
    assert amendment.previous_terms["amount"] == "1000.00"
    assert amendment.previous_terms["project_end_date"] == award.project_end_date.isoformat()


@pytest.mark.django_db
def test_approve_amendment_applies_new_terms(applicant_user, institution, grants_manager_user):
    award = _award_for(applicant_user, institution, suffix="apply")
    new_end = (date.today() + timedelta(days=365)).isoformat()
    amendment = create_award_amendment(
        award,
        change_summary="Extend end date",
        new_terms={"project_end_date": new_end, "amount": "1500"},
        actor=grants_manager_user,
    )
    approve_award_amendment(amendment, actor=grants_manager_user)

    amendment = AwardAmendment.objects.get(pk=amendment.pk)
    assert amendment.status == AwardAmendment.Status.APPROVED
    assert amendment.decided_at is not None
    award.refresh_from_db()
    assert award.project_end_date.isoformat() == new_end
    assert award.amount == Decimal("1500")


@pytest.mark.django_db
def test_reject_amendment_preserves_award_terms(applicant_user, institution, grants_manager_user):
    award = _award_for(applicant_user, institution, suffix="reject")
    original_end = award.project_end_date
    amendment = create_award_amendment(
        award,
        change_summary="Extend end date",
        new_terms={"project_end_date": (date.today() + timedelta(days=365)).isoformat()},
        actor=grants_manager_user,
    )
    reject_award_amendment(amendment, reason="Budget impact not justified", actor=grants_manager_user)

    amendment = AwardAmendment.objects.get(pk=amendment.pk)
    assert amendment.status == AwardAmendment.Status.REJECTED
    assert "Budget impact" in amendment.rejection_reason
    award.refresh_from_db()
    assert award.project_end_date == original_end


@pytest.mark.django_db
def test_reject_requires_reason(applicant_user, institution, grants_manager_user):
    award = _award_for(applicant_user, institution, suffix="reason")
    amendment = create_award_amendment(award, change_summary="Nope", new_terms={}, actor=grants_manager_user)
    with pytest.raises(ValidationError):
        reject_award_amendment(amendment, reason="  ", actor=grants_manager_user)
    assert AwardAmendment.objects.get(pk=amendment.pk).status == AwardAmendment.Status.REQUESTED


@pytest.mark.django_db
def test_terminal_amendments_cannot_be_redecided(applicant_user, institution, grants_manager_user):
    award = _award_for(applicant_user, institution, suffix="terminal")
    amendment = create_award_amendment(award, change_summary="x", new_terms={}, actor=grants_manager_user)
    approve_award_amendment(amendment, actor=grants_manager_user)
    amendment = AwardAmendment.objects.get(pk=amendment.pk)
    with pytest.raises(TransitionNotAllowed):
        amendment.reject()
