"""Permission-boundary tests for assistant tools.

The bot must never surface data outside what the user could see in the UI.
Each tool is invoked twice: once as a user with access, once as a user
without — and the difference must match `Document.objects.for_user(user)`.
"""
from __future__ import annotations

import uuid

import pytest

from apps.core.assistant.tools import build_tools


def _tool_named(tools, name):
    return next((t for t in tools if t.name == name), None)


@pytest.mark.django_db
def test_tools_role_gating(learner_user, alumni_user, admin_user):
    """Role-gated tools are absent for non-eligible roles."""
    learner_tools = {t.name for t in build_tools(learner_user)}
    alumni_tools = {t.name for t in build_tools(alumni_user)}
    admin_tools = {t.name for t in build_tools(admin_user)}

    # Every user gets the universal tools.
    universals = {"whoami", "platform_help", "search_repository_documents",
                  "get_document_details", "list_open_grant_calls"}
    assert universals.issubset(learner_tools)
    assert universals.issubset(alumni_tools)
    assert universals.issubset(admin_tools)

    # Alumni events tool only for alumni-tier users (and admins).
    assert "list_upcoming_alumni_events" in alumni_tools
    assert "list_upcoming_alumni_events" in admin_tools
    assert "list_upcoming_alumni_events" not in learner_tools

    # count_my_open_applications gated by RIMS_APPLICANT_ROLES (scholar / researcher /
    # applicant); a learner is none of those.
    assert "count_my_open_applications" not in learner_tools


@pytest.mark.django_db
def test_search_filters_by_for_user_visibility(submitter_user, learner_user, public_collection, sample_pdf):
    """Documents in DRAFT status visible to their owner are NOT visible
    to other users — and the assistant search must respect this."""
    from apps.repository.documents.models import Document

    draft = Document.objects.create(
        title="Hermetic storage in maize — secret draft",
        abstract="Field trial draft only the author should see.",
        document_type=Document.DocumentType.THESIS,
        status=Document.Status.DRAFT,
        collection=public_collection,
        submitted_by=submitter_user,
        file=sample_pdf,
        public_document_id="REPO-TEST-HERM",
    )

    submitter_search = _tool_named(build_tools(submitter_user), "search_repository_documents")
    learner_search = _tool_named(build_tools(learner_user), "search_repository_documents")
    assert submitter_search and learner_search

    submitter_hits = submitter_search.invoke({"query": "hermetic storage", "k": 5})
    learner_hits = learner_search.invoke({"query": "hermetic storage", "k": 5})

    submitter_pids = {h["public_document_id"] for h in submitter_hits}
    learner_pids = {h["public_document_id"] for h in learner_hits}

    assert draft.public_document_id in submitter_pids
    assert draft.public_document_id not in learner_pids


@pytest.mark.django_db
def test_get_document_details_404s_for_unauthorized(submitter_user, learner_user, public_collection, sample_pdf):
    """A learner asking for a draft document by ID must get a not-found
    response, not the document body."""
    from apps.repository.documents.models import Document

    draft = Document.objects.create(
        title="Confidential draft",
        abstract="This must not leak.",
        document_type=Document.DocumentType.THESIS,
        status=Document.Status.DRAFT,
        collection=public_collection,
        submitted_by=submitter_user,
        file=sample_pdf,
        public_document_id="REPO-TEST-CONF",
    )

    learner_detail = _tool_named(build_tools(learner_user), "get_document_details")
    submitter_detail = _tool_named(build_tools(submitter_user), "get_document_details")

    assert learner_detail.invoke({"public_document_id": draft.public_document_id}) == {
        "error": "not_found_or_not_accessible"
    }
    submitter_result = submitter_detail.invoke({"public_document_id": draft.public_document_id})
    assert submitter_result.get("title") == "Confidential draft"


@pytest.mark.django_db
def test_get_document_details_unknown_id_returns_error(learner_user):
    detail_tool = _tool_named(build_tools(learner_user), "get_document_details")
    result = detail_tool.invoke({"public_document_id": "DOES-NOT-EXIST"})
    assert result == {"error": "not_found_or_not_accessible"}


# ── Wave 2 tests ──────────────────────────────────────────────────────────


@pytest.mark.django_db
def test_universal_tools_present_for_every_role(
    learner_user, alumni_user, admin_user, submitter_user, repo_manager_user
):
    """Tools every authenticated user should see, regardless of role."""
    # NOTE: list_active_courses was removed with the REP LMS (learning is on Moodle).
    expected = {"whoami", "platform_help", "search_repository_documents",
                "get_document_details", "list_open_grant_calls",
                "list_my_unread_notifications",
                "list_repository_collections"}
    for u in (learner_user, alumni_user, admin_user, submitter_user, repo_manager_user):
        names = {t.name for t in build_tools(u)}
        assert expected.issubset(names), (
            f"missing universals for role={u.role}: {expected - names}"
        )


@pytest.mark.django_db
def test_role_gated_tools_only_appear_for_eligible_roles(
    learner_user, alumni_user, admin_user, submitter_user, repo_manager_user
):
    """Each role-gated tool must register exactly for the right roles."""
    learner = {t.name for t in build_tools(learner_user)}
    alumni = {t.name for t in build_tools(alumni_user)}
    admin = {t.name for t in build_tools(admin_user)}
    submitter = {t.name for t in build_tools(submitter_user)}
    repo_mgr = {t.name for t in build_tools(repo_manager_user)}

    # list_my_repository_submissions is available to every authenticated user
    # (filter is `submitted_by=user` — non-submitters just get []).
    for role_set in (submitter, admin, learner, repo_mgr, alumni):
        assert "list_my_repository_submissions" in role_set

    # Applicant-only (RIMS_APPLICANT_ROLES = applicant + scholar + researcher).
    assert "list_my_applications" in submitter
    assert "list_my_applications" in admin
    assert "list_my_applications" not in learner
    assert "list_my_applications" not in repo_mgr
    assert "list_my_applications" not in alumni

    # Alumni-only.
    assert "list_my_mentorships" in alumni
    assert "list_my_mentorships" in admin
    assert "list_my_mentorships" not in learner
    assert "list_my_mentorships" not in submitter

    # Reviewer-only — no reviewer fixture; just confirm absence for non-reviewers.
    for role_set in (learner, alumni, submitter, repo_mgr):
        assert "list_my_pending_reviews" not in role_set
    assert "list_my_pending_reviews" in admin

    # Entrepreneur-only.
    for role_set in (learner, alumni, submitter, repo_mgr):
        assert "list_my_smehub_businesses" not in role_set
        assert "list_my_funding_applications" not in role_set
    assert "list_my_smehub_businesses" in admin
    assert "list_my_funding_applications" in admin

    # SME admin / admin only.
    for role_set in (learner, alumni, submitter, repo_mgr):
        assert "count_pending_verifications" not in role_set
    assert "count_pending_verifications" in admin

    # MEL/management-only.
    for role_set in (learner, alumni, submitter, repo_mgr):
        assert "list_at_risk_activities" not in role_set
    assert "list_at_risk_activities" in admin

    # list_my_courses was removed with the REP LMS (learning is on Moodle); no
    # role should still expose it.
    for role_set in (learner, alumni, admin, submitter, repo_mgr):
        assert "list_my_courses" not in role_set


@pytest.mark.django_db
def test_list_my_repository_submissions_scopes_to_owner(
    submitter_user, learner_user, public_collection, sample_pdf
):
    """Only the submitter sees their own document, regardless of status."""
    from apps.repository.documents.models import Document

    Document.objects.create(
        title="My published thesis",
        abstract="Mine.",
        document_type=Document.DocumentType.THESIS,
        status=Document.Status.PUBLISHED,
        collection=public_collection,
        submitted_by=submitter_user,
        file=sample_pdf,
        public_document_id="REPO-TEST-MINE",
    )

    submitter_tool = _tool_named(build_tools(submitter_user), "list_my_repository_submissions")
    assert submitter_tool is not None
    rows = submitter_tool.invoke({"limit": 10})
    assert any(r["public_document_id"] == "REPO-TEST-MINE" for r in rows)

    # learner is registered for the tool too (anyone can submit), but their
    # own filter returns []: they didn't submit anything.
    learner_tool = _tool_named(build_tools(learner_user), "list_my_repository_submissions")
    assert learner_tool is not None
    assert learner_tool.invoke({"limit": 10}) == []


@pytest.mark.django_db
def test_list_my_applications_scopes_to_applicant(submitter_user, alumni_user):
    """Each applicant sees only their own applications."""
    from apps.rims.grants.models import Application, GrantCall
    from django.utils import timezone
    import datetime

    call = GrantCall.objects.create(
        title="Test call",
        slug="test-call-bot",
        status=GrantCall.Status.PUBLISHED,
        opens_at=timezone.now(),
        closes_at=timezone.now() + datetime.timedelta(days=30),
    )
    Application.objects.create(
        call=call, applicant=submitter_user, status=Application.Status.SUBMITTED
    )

    s_tool = _tool_named(build_tools(submitter_user), "list_my_applications")
    assert s_tool is not None
    s_rows = s_tool.invoke({"limit": 10})
    assert any(r["call_title"] == "Test call" for r in s_rows)

    # alumni_user has no applicant role — tool not registered.
    a_tool = _tool_named(build_tools(alumni_user), "list_my_applications")
    assert a_tool is None


@pytest.mark.django_db
def test_list_my_unread_notifications_scopes_to_recipient(learner_user, alumni_user):
    """Only the recipient sees their own unread notifications."""
    from apps.core.notifications.models import Notification

    Notification.objects.create(
        recipient=learner_user, message="Hello learner", is_read=False
    )
    learner_tool = _tool_named(build_tools(learner_user), "list_my_unread_notifications")
    alumni_tool = _tool_named(build_tools(alumni_user), "list_my_unread_notifications")

    learner_result = learner_tool.invoke({"limit": 5})
    alumni_result = alumni_tool.invoke({"limit": 5})

    assert learner_result["count"] == 1
    titles = [it["title"] for it in learner_result["items"]]
    assert any("Hello learner" in t for t in titles)
    assert alumni_result["count"] == 0
    assert alumni_result["items"] == []
