"""
Context processor: nav_show_* booleans
=======================================
Injects sidebar visibility flags based on the user's role and permissions.
Each flag controls whether a top-level navigation section is rendered.

Roles that are RIMS-focused (grants_manager, finance_*, program_*, reviewer,
mel_officer, auditor, programme_officer, project_manager, program_coordinator,
finance_manager, and the funding-administration roles below) should NOT see
REP, Alumni, or SME Hub — enforced by ``nav_show_rims`` vs. the other flags.
NB: the Program Coordinator sees BOTH RIMS and M&EL — RIMS because SRS §5.4
FRFM011/029 makes them the canonical budget-revision approver / sole budget
editor, and M&EL because Progress Reporting (submitting implementation data) is
their core work. They get the scoped ``nav_mel_officer=False`` M&EL menu.

Roles that are REP/learning-focused (learner, instructor, rep_learning_admin)
should NOT see RIMS management sections (but can see their own RIMS
applications if they applied for a grant).

Every top-level module flag (``nav_show_rep``, ``nav_show_alumni``,
``nav_show_repository``, ``nav_show_smehub``, ``nav_show_rims``, ``nav_show_mel``)
must have an explicit role allowlist below — a module with no allowlist renders
for every authenticated user regardless of role, which is a real bug that
shipped for RIMS (see the 2026-07-02 Repository-roles report: any user,
including staff with zero RIMS involvement, saw the RIMS nav item
unconditionally). M&EL was previously gated by an inline template role-literal
(no flag); the 2026-07-23 fix promoted it to ``nav_show_mel`` /
``nav_mel_officer`` so it follows the same allowlist invariant.

Admin / system_admin see everything.
"""

from django.conf import settings

from apps.core.permissions.roles import UserRole


def _moodle_url() -> str:
    """Browser-facing Moodle base URL (learning lives in Moodle now)."""
    host = (getattr(settings, "MOODLE_HOST", "") or "localhost:8081").strip()
    if host.startswith(("http://", "https://")):
        return host.rstrip("/")
    scheme = "https" if not host.startswith("localhost") and ":" not in host.split(".")[0] else "http"
    return f"{scheme}://{host}".rstrip("/")


# Roles that should see the REP learning section
_REP_ROLES = frozenset({
    UserRole.LEARNER.value,
    UserRole.INSTRUCTOR.value,
    UserRole.REP_LEARNING_ADMIN.value,
    UserRole.SCHOLAR.value,       # scholars may take courses
    UserRole.ALUMNI.value,        # alumni can access learning
    UserRole.APPLICANT.value,     # applicants can browse catalogue
    UserRole.ADMIN.value,
    UserRole.SYSTEM_ADMIN.value,
})

# Roles that should see the Alumni section
_ALUMNI_ROLES = frozenset({
    UserRole.ALUMNI.value,
    UserRole.ALUMNI_OFFICER.value,
    UserRole.MEL_OFFICER.value,
    UserRole.SCHOLAR.value,
    UserRole.ADMIN.value,
    UserRole.SYSTEM_ADMIN.value,
})

# Roles that should see the SME Hub section
_SMEHUB_ROLES = frozenset({
    UserRole.ENTREPRENEUR.value,
    UserRole.MENTOR.value,
    UserRole.INVESTOR.value,
    UserRole.BUYER.value,
    UserRole.SERVICE_PROVIDER.value,
    UserRole.SME_ADMIN.value,
    UserRole.JUDGE.value,
    UserRole.PARTNER.value,
    UserRole.ADMIN.value,
    UserRole.SYSTEM_ADMIN.value,
})

# Roles that should see the Repository section
_REPOSITORY_ROLES = frozenset({
    UserRole.RESEARCHER.value,
    UserRole.REPO_MANAGER.value,
    UserRole.GRANTS_MANAGER.value,
    UserRole.SCHOLAR.value,
    UserRole.INSTRUCTOR.value,
    UserRole.LEARNER.value,
    UserRole.ALUMNI.value,
    UserRole.APPLICANT.value,
    UserRole.PROGRAM_MANAGER.value,
    UserRole.PROGRAM_DIRECTOR.value,
    UserRole.MEL_OFFICER.value,
    UserRole.ADMIN.value,
    UserRole.SYSTEM_ADMIN.value,
    UserRole.REP_LEARNING_ADMIN.value,
    UserRole.KHUB_MANAGER.value,
    UserRole.KNOWLEDGE_MANAGEMENT_OFFICER.value,
    UserRole.COLLABORATOR.value,
    UserRole.REPOSITORY_ADMIN.value,
    UserRole.EDITOR_IN_CHIEF.value,
    UserRole.DIGITAL_ACADEMY_OFFICER.value,
    # Reviewers assess repository submissions, so they need the Repository
    # section (previously they saw only RIMS).
    UserRole.REVIEWER.value,
    # Publication Assistants run SP6 compliance/licensing checks in the QA queue.
    UserRole.PUBLICATION_ASSISTANT.value,
})

# Roles that should see the RIMS section — staff/management roles seeded with
# RIMS capability permissions (apps/core/management/commands/seed_roles.py
# _attach_rims_permissions) or gated via a RIMS mixin, plus applicants/scholars
# who track their own applications. Previously the RIMS nav item had NO gate
# at all (visible to every authenticated user, including roles with zero RIMS
# involvement) — see the 2026-07-02 Repository-roles bug report.
_RIMS_ROLES = frozenset({
    UserRole.ADMIN.value,
    UserRole.SYSTEM_ADMIN.value,
    UserRole.GRANTS_MANAGER.value,
    UserRole.PROGRAM_MANAGER.value,
    UserRole.PROGRAM_DIRECTOR.value,
    UserRole.FINANCE_OFFICER.value,
    UserRole.FINANCE_DIRECTOR.value,
    UserRole.REVIEWER.value,
    UserRole.MEL_OFFICER.value,
    UserRole.PROGRAMME_OFFICER.value,
    UserRole.AUDITOR.value,
    UserRole.FINANCE_MANAGER.value,
    # Program Coordinator keeps RIMS: per SRS §5.4 FRFM011/029 they are the
    # canonical approver of budget revisions and the ONLY role allowed to edit
    # budgets (apps/rims/finance BudgetUpdateView / approve_budget_revision), so
    # they need the RIMS Finance surfaces. (Their core work is still M&E
    # Progress Reporting — they also get the scoped M&EL nav via nav_show_mel.)
    UserRole.PROGRAM_COORDINATOR.value,
    UserRole.PROJECT_MANAGER.value,
    UserRole.GRANTS_VALIDATOR.value,
    UserRole.PRINCIPAL_INVESTIGATOR.value,
    UserRole.SCHOLARSHIP_MANAGER.value,
    UserRole.SCHOLARSHIP_REVIEWER.value,
    UserRole.SCHOLARSHIP_VALIDATOR.value,
    UserRole.PLANNING_MONITORING_EVALUATION.value,
    UserRole.HUMAN_RESOURCE.value,
    UserRole.SCHOLARSHIP_HOME_VALIDATOR.value,
    UserRole.APPLICANT.value,
    UserRole.SCHOLAR.value,
})

# Roles that see the FULL M&EL section (all seven SRS surfaces — results
# frameworks, indicators, tracking, validation, compliance, reports). These are
# the M&E Officers and the management tier that owns the results framework.
_MEL_OFFICER_NAV_ROLES = frozenset({
    UserRole.ADMIN.value,
    UserRole.SYSTEM_ADMIN.value,
    UserRole.MEL_OFFICER.value,
    UserRole.PROGRAM_MANAGER.value,
    UserRole.PROGRAM_DIRECTOR.value,
    UserRole.GRANTS_MANAGER.value,
    UserRole.SME_ADMIN.value,
})

# Progress-Reporting field roles (M&E SRS §4.4.3 / Table 65): Program
# Coordinators / Implementers submit implementation data for the indicators
# they're responsible for. They get a SCOPED M&EL menu (submit + my
# submissions only) — not the officer surfaces they can't reach. Mirrors
# apps.core.dashboard.role_groups.MEL_IMPLEMENTER_ROLES.
_MEL_IMPLEMENTER_NAV_ROLES = frozenset({
    UserRole.PROGRAM_COORDINATOR.value,
    UserRole.PROJECT_MANAGER.value,
    UserRole.PRINCIPAL_INVESTIGATOR.value,
})


def nav_visibility(request):
    """
    Returns nav_show_rep, nav_show_alumni, nav_show_repository, nav_show_rims
    booleans for use in sidebar.html / topnav.html.
    """
    user = request.user

    moodle_url = _moodle_url()
    # "Learning" points at Moodle's SSO login deep-link. Theme JS (autoStartIilmpSso
    # in moodle/theme/iilmp/javascript/iilmp.js) makes BOTH session states seamless:
    #   • logged OUT of Moodle → auto-follows the "Log in with IILMP" button (the
    #     signed-in IILMP session lands straight in Moodle, no login page);
    #   • already logged IN to Moodle → Moodle would show "already logged in as X,
    #     log out first"; the JS detects that (no IdP button on a ?authprovider page)
    #     and redirects to /my/ instead. Same user, so the dashboard is the intent.
    # A bare /login/index.php visit (no authprovider param) still shows the manual
    # form — that's the escape hatch for local Moodle accounts.
    moodle_learning_url = f"{moodle_url}/login/index.php?authprovider=iilmp"

    if not user.is_authenticated:
        return {
            "nav_show_rep": False,
            "nav_show_alumni": False,
            "nav_show_repository": False,
            "nav_show_smehub": False,
            "nav_show_rims": False,
            "nav_show_mel": False,
            "nav_mel_officer": False,
            "nav_rims_finance_only": False,
            "nav_repo_manage": False,
            "nav_repo_qa": False,
            "nav_repo_analytics": False,
            "moodle_url": moodle_url,
            "moodle_learning_url": moodle_learning_url,
        }

    if user.is_superuser:
        return {
            "nav_show_rep": True,
            "nav_show_alumni": True,
            "nav_show_repository": True,
            "nav_show_smehub": True,
            "nav_show_rims": True,
            "nav_show_mel": True,
            "nav_mel_officer": True,
            "nav_rims_finance_only": False,
            "nav_repo_manage": True,
            "nav_repo_qa": True,
            "nav_repo_analytics": True,
            "moodle_url": moodle_url,
            "moodle_learning_url": moodle_learning_url,
        }

    role = getattr(user, "role", "") or ""

    # Repository nav tiers — import here to avoid a circular import at module
    # load (permissions.py is safe to import lazily; it has no nav deps).
    from apps.repository.documents.permissions import (
        QA_PARTICIPANT_ROLES,
        REPOSITORY_MANAGEMENT_ROLES,
    )

    return {
        # nav_show_rep now gates the "Learning" link out to Moodle.
        "nav_show_rep": role in _REP_ROLES,
        "nav_show_alumni": role in _ALUMNI_ROLES,
        "nav_show_repository": role in _REPOSITORY_ROLES,
        "nav_show_smehub": role in _SMEHUB_ROLES,
        "nav_show_rims": role in _RIMS_ROLES,
        # M&EL — officers get the full seven-surface menu; Program
        # Coordinators / Implementers get a scoped submit-and-track menu.
        "nav_show_mel": (
            role in _MEL_OFFICER_NAV_ROLES or role in _MEL_IMPLEMENTER_NAV_ROLES
        ),
        "nav_mel_officer": role in _MEL_OFFICER_NAV_ROLES,
        # Program Coordinators reach RIMS only for their Finance/budget duty
        # (SRS §5.4 FRFM011/029 — canonical budget-revision approver / sole
        # budget editor). Scope the RIMS mega-menu to the Finance column for
        # them; grants / funding / operations / projects / scholarships are not
        # their remit and stay hidden.
        "nav_rims_finance_only": role == UserRole.PROGRAM_COORDINATOR.value,
        # Manage column (QA queue, access requests, violations, analytics) —
        # previously hardcoded to admin/repo_manager/system_admin in three
        # template blocks, hiding it from KMO / K-Hub / Repository Admin /
        # Editor-in-Chief who all hold the access.
        "nav_repo_manage": role in REPOSITORY_MANAGEMENT_ROLES,
        # QA queue link only — Reviewers + Publication Assistants (SP6).
        "nav_repo_qa": role in QA_PARTICIPANT_ROLES,
        # Analytics submenu — management tier + M&E Officer (SP9: MEL officers
        # pull repository usage as M&E indicators). Broader than nav_repo_manage
        # so MEL officers get an analytics nav link, not just URL access.
        "nav_repo_analytics": (
            role in REPOSITORY_MANAGEMENT_ROLES or role == UserRole.MEL_OFFICER
        ),
        "moodle_url": moodle_url,
        "moodle_learning_url": moodle_learning_url,
    }


def nav_notifications(request):
    """Seed the topnav bell badge with the real unread count on first paint.

    Without this the badge is hardcoded to ``0`` in the template and only
    corrects itself after the first HTMX poll (up to 45s later) — so a user
    with unread notifications sees an empty bell on every fresh page load.
    Keep the query cheap and never let it break page rendering.
    """
    user = getattr(request, "user", None)
    if not user or not user.is_authenticated:
        return {"nav_unread_notifications": 0}
    try:
        from apps.core.notifications.models import Notification

        count = Notification.objects.filter(recipient=user, is_read=False).count()
    except Exception:  # noqa: BLE001 — a notifications hiccup must not 500 the page
        count = 0
    return {"nav_unread_notifications": count}
