"""PRD §5.1 FRFA-CS013 / FRFA-CS021 / Table 14 — funding-type variants
applied at submit_application time.
"""

from __future__ import annotations

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

import pytest
from django.core.exceptions import ValidationError
from django.core.files.uploadedfile import SimpleUploadedFile
from django.utils import timezone

from apps.core.authentication.models import Institution
from apps.rims.grants.models import (
    AcademicProgressReport,
    Application,
    ApplicationDocument,
    Award,
    GrantCall,
)
from apps.rims.grants.services import submit_application


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


def _call(call_type: str, **kwargs) -> GrantCall:
    base = {
        "title": f"Call {call_type}",
        "slug": f"variant-{call_type}",
        "opens_at": timezone.now() - timedelta(days=1),
        "closes_at": timezone.now() + timedelta(days=14),
        "status": GrantCall.Status.PUBLISHED,
        "call_type": call_type,
    }
    base.update(kwargs)
    return GrantCall.objects.create(**base)


# ---------------------------------------------------------------------------
# FRFA-CS021 — budget at submit varies per call type
# ---------------------------------------------------------------------------
@pytest.mark.django_db
def test_grant_call_requires_budget_when_flag_enabled(applicant_user, institution):
    """Opt-in: when applicant_budget_required is set, the gate fires."""
    call = _call("grant", applicant_budget_required=True)
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    with pytest.raises(ValidationError):
        submit_application(app)


@pytest.mark.django_db
def test_grant_call_passes_without_budget_when_flag_off(applicant_user, institution):
    """Default behaviour: the budget rule is opt-in so legacy calls keep
    working without retro-fitting a budget attachment."""
    call = _call("grant")  # applicant_budget_required defaults to False
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    submit_application(app)
    app = Application.objects.get(pk=app.pk)
    assert app.status in {
        Application.Status.SUBMITTED,
        Application.Status.UNDER_REVIEW,
    }


@pytest.mark.django_db
def test_grant_call_accepts_with_budget_attachment_when_required(applicant_user, institution):
    call = _call("grant", applicant_budget_required=True)
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    ApplicationDocument.objects.create(
        application=app,
        label="Budget",
        file=SimpleUploadedFile("budget.pdf", b"%PDF-1.4 fake"),
        is_budget=True,
    )
    submit_application(app)
    app = Application.objects.get(pk=app.pk)
    assert app.status in {
        Application.Status.SUBMITTED,
        Application.Status.UNDER_REVIEW,  # auto-screening may advance
    }


@pytest.mark.django_db
def test_scholarship_call_rejects_budget_attachment(applicant_user, institution):
    call = _call("scholarship")
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    ApplicationDocument.objects.create(
        application=app,
        label="Budget (should not be here)",
        file=SimpleUploadedFile("budget.pdf", b"%PDF-1.4 fake"),
        is_budget=True,
    )
    with pytest.raises(ValidationError):
        submit_application(app)


@pytest.mark.django_db
def test_fellowship_call_rejects_budget_attachment(applicant_user, institution):
    call = _call("fellowship")
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    ApplicationDocument.objects.create(
        application=app,
        label="Budget",
        file=SimpleUploadedFile("budget.pdf", b"%PDF-1.4 fake"),
        is_budget=True,
    )
    with pytest.raises(ValidationError):
        submit_application(app)


@pytest.mark.django_db
def test_challenge_call_rejects_budget_attachment(applicant_user, institution):
    call = _call("challenge")
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    ApplicationDocument.objects.create(
        application=app,
        label="Budget",
        file=SimpleUploadedFile("budget.pdf", b"%PDF-1.4 fake"),
        is_budget=True,
    )
    with pytest.raises(ValidationError):
        submit_application(app)


# ---------------------------------------------------------------------------
# FRFA-CS013 — scholarship-specific eligibility inputs
# ---------------------------------------------------------------------------
@pytest.mark.django_db
def test_scholarship_call_requires_university_when_flagged(applicant_user, institution):
    call = _call("scholarship", scholarship_university_required=True)
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    with pytest.raises(ValidationError):
        submit_application(app)


@pytest.mark.django_db
def test_scholarship_call_requires_course_when_flagged(applicant_user, institution):
    call = _call("scholarship", scholarship_course_required=True)
    app = Application.objects.create(
        call=call,
        applicant=applicant_user,
        institution=institution,
        scholarship_university_choice=institution,
    )
    with pytest.raises(ValidationError):
        submit_application(app)


@pytest.mark.django_db
def test_scholarship_call_passes_when_all_inputs_present(applicant_user, institution):
    call = _call(
        "scholarship",
        scholarship_university_required=True,
        scholarship_course_required=True,
    )
    app = Application.objects.create(
        call=call,
        applicant=applicant_user,
        institution=institution,
        scholarship_university_choice=institution,
        scholarship_course_choice="MSc Agroecology",
    )
    submit_application(app)
    app = Application.objects.get(pk=app.pk)
    assert app.status in {
        Application.Status.SUBMITTED,
        Application.Status.UNDER_REVIEW,
    }


# ---------------------------------------------------------------------------
# AcademicProgressReport
# ---------------------------------------------------------------------------
@pytest.mark.django_db
def test_academic_progress_report_unique_per_period(applicant_user, institution):
    from datetime import date

    call = _call("scholarship")
    app = Application.objects.create(
        call=call,
        applicant=applicant_user,
        institution=institution,
        scholarship_course_choice="MSc",
    )
    award = Award.objects.create(
        application=app,
        amount=Decimal("1000"),
        currency="USD",
        project_end_date=date.today() + timedelta(days=365),
    )
    AcademicProgressReport.objects.create(award=award, period_label="Sem 1 2026")
    with pytest.raises(Exception):  # DB IntegrityError on the unique constraint
        AcademicProgressReport.objects.create(award=award, period_label="Sem 1 2026")
