"""Project IILMP roles onto Moodle system-context roles.

SSO ("Log in with IILMP") creates Moodle users as plain authenticated users, so
IILMP instructors/admins land in Moodle powerless. This module assigns the few
roles that mean something at Moodle's SYSTEM context (``settings.MOODLE_ROLE_MAP``:
admins → manager, instructors → course creator) via the same Web Services token
the MEL pull uses. Everything else is deliberately left unmapped — course roles
(student/teacher) come from enrolment, which is correct Moodle semantics.

Assign happens automatically at SSO login (see ``oidc.IILMPOAuth2Validator``);
demotions only run through the ``sync_moodle_roles`` management command with
``--demote``, so a login can never *remove* access.
"""
from __future__ import annotations

import logging

from django.conf import settings

from apps.mel.tracking.moodle_client import MoodleClient, MoodleError

logger = logging.getLogger(__name__)

# Outcomes returned by sync_user() — stable strings for logging/tests.
OUTCOME_DISABLED = "disabled"
OUTCOME_UNMAPPED = "unmapped"
OUTCOME_NOT_IN_MOODLE = "not_in_moodle"
OUTCOME_ASSIGNED = "assigned"
OUTCOME_DEMOTED = "demoted"
OUTCOME_NOOP = "noop"


def desired_moodle_role(user) -> str | None:
    """Return the Moodle role shortname mapped to the user's IILMP role, if any."""
    role = getattr(user, "role", "") or ""
    return settings.MOODLE_ROLE_MAP.get(role)


def resolve_moodle_user_id(user, client: MoodleClient) -> int | None:
    """Find the user's Moodle id — by ``lms_ref`` back-link, else by email.

    An email hit persists ``lms_ref`` so future syncs skip the lookup.
    """
    if user.lms_ref:
        return int(user.lms_ref)
    found = client.call(
        "core_user_get_users_by_field", field="email", values=[user.email]
    )
    if not found:
        return None
    moodle_id = int(found[0]["id"])
    user.lms_ref = moodle_id
    user.save(update_fields=["lms_ref"])
    return moodle_id


def sync_user(user, *, demote: bool = False, client: MoodleClient | None = None) -> str:
    """Assign (and with ``demote=True`` also revoke) the mapped Moodle role.

    Idempotent: Moodle's role_assign on an existing assignment is a no-op, and
    role_unassign of a missing assignment deletes nothing. Returns an OUTCOME_*
    string describing what happened.
    """
    if not settings.MOODLE_ROLE_SYNC_ENABLED:
        return OUTCOME_DISABLED

    desired = desired_moodle_role(user)
    if desired is None and not demote:
        return OUTCOME_UNMAPPED

    client = client or MoodleClient()
    if not client.configured:
        logger.warning("moodle role sync skipped: MoodleClient not configured")
        return OUTCOME_DISABLED

    moodle_id = resolve_moodle_user_id(user, client)
    if moodle_id is None:
        return OUTCOME_NOT_IN_MOODLE

    context_id = settings.MOODLE_SYSTEM_CONTEXT_ID
    role_ids = settings.MOODLE_ROLE_IDS

    if desired is not None:
        client.call(
            "core_role_assign_roles",
            assignments=[
                {
                    "roleid": role_ids[desired],
                    "userid": moodle_id,
                    "contextid": context_id,
                }
            ],
        )
        logger.info("moodle role sync: %s → %s (moodle user %s)", user.email, desired, moodle_id)

    if demote:
        # Strip every IILMP-managed role the user should no longer hold.
        to_remove = [name for name in role_ids if name != desired]
        if to_remove:
            client.call(
                "core_role_unassign_roles",
                unassignments=[
                    {
                        "roleid": role_ids[name],
                        "userid": moodle_id,
                        "contextid": context_id,
                    }
                    for name in to_remove
                ],
            )
        if desired is None:
            logger.info("moodle role sync: %s demoted (moodle user %s)", user.email, moodle_id)
            return OUTCOME_DEMOTED

    return OUTCOME_ASSIGNED if desired is not None else OUTCOME_NOOP


__all__ = [
    "MoodleError",
    "desired_moodle_role",
    "resolve_moodle_user_id",
    "sync_user",
    "OUTCOME_DISABLED",
    "OUTCOME_UNMAPPED",
    "OUTCOME_NOT_IN_MOODLE",
    "OUTCOME_ASSIGNED",
    "OUTCOME_DEMOTED",
    "OUTCOME_NOOP",
]
