import ForbiddenError from '@/shared/errors/forbidden';
import { ROLE_NAMES, type RoleName } from '@/shared/constants/roles';
import type { CurrentUser } from '@/db/api/types';

/**
 * Relational role constraints (Workstream 3 §3.3) — the spec's "unlimited
 * except …" rules. These depend on *which role the target holds*, not just the
 * verb/entity, so a flat `${METHOD}_${ENTITY}` permission cannot express them.
 * Enforced in the service layer (authoritative); tenant/campus scoping is
 * enforced separately in the data layer.
 */

const ALL_ROLE_NAMES: readonly RoleName[] = Object.values(ROLE_NAMES);

/**
 * The roles each actor may create, re-assign, or delete on another user:
 * - `super_admin`: anyone.
 * - `system_admin`: anyone except `super_admin` / `system_admin`.
 * - `owner`: org/campus/external roles (not the system roles, not another owner).
 * - `superintendent`: not `owner` / `superintendent` (per spec).
 * - `director`: campus + external roles only (not director/superintendent/owner/admins).
 * - everyone else: nobody.
 */
const MANAGEABLE_ROLES_BY_ACTOR: Record<RoleName, readonly RoleName[]> = {
  [ROLE_NAMES.SUPER_ADMIN]: ALL_ROLE_NAMES,
  [ROLE_NAMES.SYSTEM_ADMIN]: [
    ROLE_NAMES.OWNER, ROLE_NAMES.SUPERINTENDENT, ROLE_NAMES.DIRECTOR,
    ROLE_NAMES.OFFICE_MANAGER, ROLE_NAMES.TEACHER, ROLE_NAMES.SUPPORT_STAFF,
    ROLE_NAMES.STUDENT, ROLE_NAMES.GUARDIAN, ROLE_NAMES.GUEST,
  ],
  [ROLE_NAMES.OWNER]: [
    ROLE_NAMES.SUPERINTENDENT, ROLE_NAMES.DIRECTOR, ROLE_NAMES.OFFICE_MANAGER,
    ROLE_NAMES.TEACHER, ROLE_NAMES.SUPPORT_STAFF, ROLE_NAMES.STUDENT,
    ROLE_NAMES.GUARDIAN, ROLE_NAMES.GUEST,
  ],
  [ROLE_NAMES.SUPERINTENDENT]: [
    ROLE_NAMES.DIRECTOR, ROLE_NAMES.OFFICE_MANAGER, ROLE_NAMES.TEACHER,
    ROLE_NAMES.SUPPORT_STAFF, ROLE_NAMES.STUDENT, ROLE_NAMES.GUARDIAN,
    ROLE_NAMES.GUEST,
  ],
  [ROLE_NAMES.DIRECTOR]: [
    ROLE_NAMES.OFFICE_MANAGER, ROLE_NAMES.TEACHER, ROLE_NAMES.SUPPORT_STAFF,
    ROLE_NAMES.STUDENT, ROLE_NAMES.GUARDIAN, ROLE_NAMES.GUEST,
  ],
  [ROLE_NAMES.OFFICE_MANAGER]: [],
  [ROLE_NAMES.TEACHER]: [],
  [ROLE_NAMES.SUPPORT_STAFF]: [],
  [ROLE_NAMES.STUDENT]: [],
  [ROLE_NAMES.GUARDIAN]: [],
  [ROLE_NAMES.GUEST]: [],
};

/** Roles allowed to delete an organization (company) profile. */
const ORGANIZATION_DELETE_ROLES: readonly RoleName[] = [
  ROLE_NAMES.SUPER_ADMIN,
  ROLE_NAMES.SYSTEM_ADMIN,
  ROLE_NAMES.OWNER,
];

export function isRoleName(value: string | null | undefined): value is RoleName {
  return value != null && (ALL_ROLE_NAMES as readonly string[]).includes(value);
}

function actorRoleName(currentUser?: CurrentUser): RoleName | null {
  const name = currentUser?.app_role?.name;
  return isRoleName(name) ? name : null;
}

/**
 * Whether `currentUser` may create/re-assign/delete a user holding `targetRole`.
 * A `null`/unknown target role is treated as below every role the actor manages
 * (so a manager may act on a not-yet-assigned user), but an actor that manages
 * nobody is always denied.
 */
export function canManageUserWithRole(
  currentUser: CurrentUser | undefined,
  targetRole: string | null | undefined,
): boolean {
  const actor = actorRoleName(currentUser);
  if (!actor) return false;
  const manageable = MANAGEABLE_ROLES_BY_ACTOR[actor];
  if (manageable.length === 0) return false;
  if (!isRoleName(targetRole)) return true;
  return manageable.includes(targetRole);
}

export function assertCanManageUserWithRole(
  currentUser: CurrentUser | undefined,
  targetRole: string | null | undefined,
): void {
  if (!canManageUserWithRole(currentUser, targetRole)) {
    throw new ForbiddenError();
  }
}

export function canDeleteOrganization(currentUser?: CurrentUser): boolean {
  const actor = actorRoleName(currentUser);
  return actor !== null && ORGANIZATION_DELETE_ROLES.includes(actor);
}

export function assertCanDeleteOrganization(currentUser?: CurrentUser): void {
  if (!canDeleteOrganization(currentUser)) {
    throw new ForbiddenError();
  }
}
