"""IDOR / programme-scope regression tests for the v1 RIMS API.

Closes the API security gaps called out in `setup/PROJECT_ASSESSMENT.md`:
  - InterviewSchedule / VerificationRecord previously returned every row to
    every authenticated user (severe IDOR).
  - ApplicationViewSet / AwardViewSet previously filtered only by role, so a
    grants_manager assigned to programme A could see programme B.

Scope predicate lives in :mod:`api.v1.permissions` — derived from
``FundingRecord.{grant_manager, program_lead, finance_officer}`` FKs. No new
membership model.
"""
from __future__ import annotations

import pytest

APPLICATIONS_URL = "/api/v1/applications/"
AWARDS_URL = "/api/v1/awards/"
INTERVIEWS_URL = "/api/v1/interviews/"
VERIFICATIONS_URL = "/api/v1/verifications/"


def _ids(response) -> set[int]:
    payload = response.json()
    rows = payload.get("results") if isinstance(payload, dict) and "results" in payload else payload
    return {row["id"] for row in rows}


@pytest.mark.django_db
def test_anonymous_cannot_list_applications(api_client):
    resp = api_client.get(APPLICATIONS_URL)
    assert resp.status_code in (401, 403)


@pytest.mark.django_db
def test_anonymous_cannot_list_interviews(api_client, interview_a):
    resp = api_client.get(INTERVIEWS_URL)
    assert resp.status_code in (401, 403)


@pytest.mark.django_db
def test_anonymous_cannot_list_verifications(api_client, verification_a):
    resp = api_client.get(VERIFICATIONS_URL)
    assert resp.status_code in (401, 403)


@pytest.mark.django_db
def test_applicant_sees_only_own_applications(
    api_client, applicant_a, application_a, application_b
):
    api_client.force_authenticate(applicant_a)
    resp = api_client.get(APPLICATIONS_URL)
    assert resp.status_code == 200
    assert _ids(resp) == {application_a.id}


@pytest.mark.django_db
def test_applicant_sees_only_own_interviews(
    api_client, applicant_a, interview_a, interview_b
):
    api_client.force_authenticate(applicant_a)
    resp = api_client.get(INTERVIEWS_URL)
    assert resp.status_code == 200
    assert _ids(resp) == {interview_a.id}


@pytest.mark.django_db
def test_applicant_sees_only_own_verifications(
    api_client, applicant_a, verification_a, verification_b
):
    api_client.force_authenticate(applicant_a)
    resp = api_client.get(VERIFICATIONS_URL)
    assert resp.status_code == 200
    assert _ids(resp) == {verification_a.id}


@pytest.mark.django_db
def test_applicant_sees_only_own_awards(
    api_client, applicant_a, award_a, award_b
):
    api_client.force_authenticate(applicant_a)
    resp = api_client.get(AWARDS_URL)
    assert resp.status_code == 200
    assert _ids(resp) == {award_a.id}


@pytest.mark.django_db
def test_grants_manager_a_cannot_see_programme_b(
    api_client,
    grants_manager_a,
    application_a,
    application_b,
    interview_a,
    interview_b,
    verification_a,
    verification_b,
    award_a,
    award_b,
):
    api_client.force_authenticate(grants_manager_a)

    apps_resp = api_client.get(APPLICATIONS_URL)
    assert apps_resp.status_code == 200
    assert _ids(apps_resp) == {application_a.id}

    iv_resp = api_client.get(INTERVIEWS_URL)
    assert iv_resp.status_code == 200
    assert _ids(iv_resp) == {interview_a.id}

    vr_resp = api_client.get(VERIFICATIONS_URL)
    assert vr_resp.status_code == 200
    assert _ids(vr_resp) == {verification_a.id}

    aw_resp = api_client.get(AWARDS_URL)
    assert aw_resp.status_code == 200
    assert _ids(aw_resp) == {award_a.id}


@pytest.mark.django_db
def test_finance_officer_sees_assigned_programme_only(
    api_client,
    finance_officer_a,
    award_a,
    award_b,
    application_a,
    application_b,
):
    api_client.force_authenticate(finance_officer_a)
    aw_resp = api_client.get(AWARDS_URL)
    assert aw_resp.status_code == 200
    assert _ids(aw_resp) == {award_a.id}

    apps_resp = api_client.get(APPLICATIONS_URL)
    assert apps_resp.status_code == 200
    assert _ids(apps_resp) == {application_a.id}


@pytest.mark.django_db
def test_admin_sees_everything(
    api_client,
    admin_user,
    application_a,
    application_b,
    interview_a,
    interview_b,
    verification_a,
    verification_b,
    award_a,
    award_b,
):
    api_client.force_authenticate(admin_user)
    assert _ids(api_client.get(APPLICATIONS_URL)) == {application_a.id, application_b.id}
    assert _ids(api_client.get(INTERVIEWS_URL)) == {interview_a.id, interview_b.id}
    assert _ids(api_client.get(VERIFICATIONS_URL)) == {verification_a.id, verification_b.id}
    assert _ids(api_client.get(AWARDS_URL)) == {award_a.id, award_b.id}


@pytest.mark.django_db
def test_unrelated_role_sees_nothing(
    api_client,
    learner_user,
    application_a,
    application_b,
    interview_a,
    interview_b,
    verification_a,
    verification_b,
):
    api_client.force_authenticate(learner_user)
    # A learner with no application of their own and no programme assignment
    # gets the empty set across all four endpoints.
    assert _ids(api_client.get(APPLICATIONS_URL)) == set()
    assert _ids(api_client.get(INTERVIEWS_URL)) == set()
    assert _ids(api_client.get(VERIFICATIONS_URL)) == set()
    assert _ids(api_client.get(AWARDS_URL)) == set()


@pytest.mark.django_db
def test_interview_detail_blocks_cross_programme_access(
    api_client, grants_manager_a, interview_b
):
    """Manager from programme A must not be able to GET programme B's interview by id."""
    api_client.force_authenticate(grants_manager_a)
    resp = api_client.get(f"{INTERVIEWS_URL}{interview_b.id}/")
    assert resp.status_code == 404


@pytest.mark.django_db
def test_verification_detail_blocks_cross_programme_access(
    api_client, grants_manager_a, verification_b
):
    api_client.force_authenticate(grants_manager_a)
    resp = api_client.get(f"{VERIFICATIONS_URL}{verification_b.id}/")
    assert resp.status_code == 404


@pytest.mark.django_db
def test_award_detail_blocks_cross_programme_access(
    api_client, grants_manager_a, award_b
):
    api_client.force_authenticate(grants_manager_a)
    resp = api_client.get(f"{AWARDS_URL}{award_b.id}/")
    assert resp.status_code == 404
