import { Op, literal } from 'sequelize';
import ForbiddenError from '@/shared/errors/forbidden';
import { ROLE_NAMES, ROLE_SCOPES } from '@/shared/constants/roles';
import { GLOBAL_BYPASS_EXCLUDED_PERMISSIONS } from '@/shared/constants/product-permissions';
import type { CurrentUser } from '@/db/api/types';

/** UUIDs are inlined into a SQL literal subquery; reject anything that is not one. */
const UUID_RE =
  /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;

/**
 * Shared tenant/scope access helpers used by the feature services. Centralizes
 * the (previously copy-pasted) organization/campus/scope checks so every module
 * resolves scope and access the same way.
 */

export function getOrganizationId(currentUser?: CurrentUser): string | null {
  if (currentUser?.activeScope) {
    return currentUser.activeScope.organizationId;
  }
  return currentUser?.organizations?.id || currentUser?.organizationId || null;
}

export function getCampusId(currentUser?: CurrentUser): string | null {
  if (currentUser?.activeScope) {
    return currentUser.activeScope.campusId;
  }
  return currentUser?.campusId || null;
}

export function getSchoolId(currentUser?: CurrentUser): string | null {
  if (currentUser?.activeScope) {
    return currentUser.activeScope.schoolId;
  }
  return currentUser?.schoolId || null;
}

export function getClassId(currentUser?: CurrentUser): string | null {
  if (currentUser?.activeScope) {
    return currentUser.activeScope.classId;
  }
  return currentUser?.classId || null;
}

/**
 * The active authorization scope (`roles.scope`), overridden by the drill-down
 * `activeScope.level` when the user has drilled into a child tenant.
 */
export function getRoleScope(
  currentUser?: CurrentUser,
): string | null | undefined {
  if (currentUser?.activeScope) {
    return currentUser.activeScope.level;
  }
  return currentUser?.app_role?.scope;
}

export function getRoleName(
  currentUser?: CurrentUser,
): string | null | undefined {
  return currentUser?.app_role?.name;
}

function ownScopeKey(currentUser?: CurrentUser): string | null {
  if (!currentUser?.id) {
    return null;
  }

  if (hasGlobalAccess(currentUser)) {
    return 'system:global';
  }

  const scope = currentUser.app_role?.scope;

  if (scope === ROLE_SCOPES.CLASS) {
    return currentUser.classId ? `class:${currentUser.classId}` : null;
  }

  if (scope === ROLE_SCOPES.CAMPUS) {
    return currentUser.campusId ? `campus:${currentUser.campusId}` : null;
  }

  if (scope === ROLE_SCOPES.SCHOOL) {
    return currentUser.schoolId ? `school:${currentUser.schoolId}` : null;
  }

  if (scope === ROLE_SCOPES.ORGANIZATION) {
    const organizationId = currentUser.organizations?.id || currentUser.organizationId || null;
    return organizationId ? `organization:${organizationId}` : null;
  }

  return null;
}

function activeScopeKey(currentUser?: CurrentUser): string | null {
  const activeScope = currentUser?.activeScope;
  if (!activeScope) {
    return null;
  }

  if (activeScope.level === 'class') {
    return activeScope.classId ? `class:${activeScope.classId}` : null;
  }

  if (activeScope.level === 'campus') {
    return activeScope.campusId ? `campus:${activeScope.campusId}` : null;
  }

  if (activeScope.level === 'school') {
    return activeScope.schoolId ? `school:${activeScope.schoolId}` : null;
  }

  return activeScope.organizationId ? `organization:${activeScope.organizationId}` : null;
}

/**
 * Personal workflows may be completed only in the user's own scope. A parent
 * user drilling into a child tenant is reading/working as that child scope, so
 * personal quiz/check-in/acknowledgment mutations must not create reportable
 * rows there.
 */
export function isActingInOwnScope(currentUser?: CurrentUser): boolean {
  if (!currentUser?.activeScope) {
    return true;
  }

  const ownKey = ownScopeKey(currentUser);
  const activeKey = activeScopeKey(currentUser);

  return Boolean(ownKey && activeKey && ownKey === activeKey);
}

/** Human-friendly name for the current user (full name, else email, else generic). */
export function getDisplayName(currentUser?: CurrentUser): string {
  const firstName = currentUser?.firstName || '';
  const lastName = currentUser?.lastName || '';
  const fullName = `${firstName} ${lastName}`.trim();

  return fullName || currentUser?.email || 'Staff Member';
}

/** organizationId is a NOT NULL column; callers guard authentication first. */
export function requireOrganizationId(currentUser?: CurrentUser): string {
  const organizationId = getOrganizationId(currentUser);
  if (!organizationId) {
    throw new ForbiddenError();
  }
  return organizationId;
}

/**
 * True if the user has global access. While drilled into a tenant
 * (`activeScope`), global access is suspended so the data is scoped to that
 * tenant rather than spanning everything.
 */
export function hasGlobalAccess(currentUser?: CurrentUser): boolean {
  if (currentUser?.activeScope) {
    return false;
  }
  return currentUser?.app_role?.globalAccess === true;
}

function hasPermissionBypass(currentUser?: CurrentUser): boolean {
  return currentUser?.app_role?.name === ROLE_NAMES.SUPER_ADMIN;
}

/**
 * Returns organizationId for filtering, or null if user has global access.
 * Throws ForbiddenError if user has neither global access nor organizationId.
 */
export function getOrganizationIdOrGlobal(
  currentUser?: CurrentUser,
): string | null {
  if (hasGlobalAccess(currentUser)) {
    return null;
  }
  const organizationId = getOrganizationId(currentUser);
  if (!organizationId) {
    throw new ForbiddenError();
  }
  return organizationId;
}

export function requireUserId(currentUser?: CurrentUser): string {
  if (!currentUser?.id) {
    throw new ForbiddenError();
  }
  return currentUser.id;
}

export function assertAuthenticatedTenantUser(currentUser?: CurrentUser): void {
  if (!currentUser?.id) {
    throw new ForbiddenError();
  }
  if (hasGlobalAccess(currentUser) || getOrganizationId(currentUser)) {
    return;
  }
  throw new ForbiddenError();
}

function permissionNamesFrom(value: unknown): string[] {
  if (!Array.isArray(value)) {
    return [];
  }
  return value
    .map((permission) => {
      if (
        permission
        && typeof permission === 'object'
        && 'name' in permission
        && typeof permission.name === 'string'
      ) {
        return permission.name;
      }
      return null;
    })
    .filter((name): name is string => name !== null);
}

/**
 * Permission-aware feature gate for service-level checks. Per-user exclusions
 * win over role permissions; per-user grants can add a permission without
 * changing the user's role.
 */
export function hasFeaturePermission(
  currentUser: CurrentUser | undefined,
  permission: string,
): boolean {
  if (
    hasPermissionBypass(currentUser)
    && !GLOBAL_BYPASS_EXCLUDED_PERMISSIONS.some((name) => name === permission)
  ) {
    return true;
  }

  if (permissionNamesFrom(currentUser?.custom_permissions_filter).includes(permission)) {
    return false;
  }

  if (permissionNamesFrom(currentUser?.custom_permissions).includes(permission)) {
    return true;
  }

  const rolePermissions = permissionNamesFrom(currentUser?.app_role?.permissions);
  if (rolePermissions.includes(permission)) {
    return true;
  }

  return false;
}

/** Campus-dimension constraint for a campus-bearing table, keyed by scope. */
export type CampusDimensionScope = {
  campusId?: string | { [Op.in]: ReturnType<typeof literal> };
};

/**
 * The campus-dimension filter for the current user, derived from the role's
 * authorization scope (`roles.scope`) instead of per-feature "tenant-wide"
 * lists. ANDs together with the organization filter the caller already applies:
 *
 * - global / organization scope → `{}` (no campus narrowing; whole organization)
 * - school scope (Principal/Registrar) → `campusId IN (campuses of the user's
 *   school)`, enforcing hard isolation between schools
 * - campus scope (Director/office_manager/teacher/support_staff) → `{ campusId }`
 *
 * Only valid for tables that carry a `campusId`. For school scope the campus set
 * is resolved with a correlated subquery so no extra round-trip or denormalized
 * `schoolId` column is needed (a campus belongs to exactly one school).
 */
export function campusDimensionScope(
  currentUser?: CurrentUser,
): CampusDimensionScope {
  if (hasGlobalAccess(currentUser)) {
    return {};
  }

  const scope = getRoleScope(currentUser);

  if (scope === ROLE_SCOPES.SCHOOL) {
    const schoolId = getSchoolId(currentUser);
    if (!schoolId) {
      return {};
    }
    if (!UUID_RE.test(schoolId)) {
      throw new ForbiddenError();
    }
    return {
      campusId: {
        [Op.in]: literal(
          `(SELECT "id" FROM "campuses" WHERE "schoolId" = '${schoolId}' AND "deletedAt" IS NULL)`,
        ),
      },
    };
  }

  // Class-scoped roles (Teacher/Support Staff) still resolve to their campus for
  // campus-keyed tables (which have no class granularity); class-level data uses
  // classDimensionScope instead.
  if (scope === ROLE_SCOPES.CAMPUS || scope === ROLE_SCOPES.CLASS) {
    const campusId = getCampusId(currentUser);
    return campusId ? { campusId } : {};
  }

  // organization / external / guest: no campus-dimension narrowing here.
  return {};
}

// Per-tenant content helpers live in the layer-neutral `shared/tenancy` so both
// the data layer and services can use them; re-exported here for service callers.
export {
  getOwnTenant,
  tenantExactWhere,
  tenantStamp,
  type ActiveTenant,
  type TenantLevel,
} from '@/shared/tenancy';

/** Class-dimension constraint for a class-keyed table, keyed by scope. */
export type ClassDimensionScope = {
  classId?: string | { [Op.in]: ReturnType<typeof literal> };
};

/**
 * The class-dimension filter for the current user, for tables that carry a
 * `classId` (class-level content, class attendance). ANDs with the organization
 * filter the caller applies:
 *
 * - global / organization scope → `{}` (no class narrowing)
 * - school scope → classes in the user's school (campus IN school)
 * - campus scope → classes in the user's campus
 * - class scope (Teacher/Support Staff) → `{ classId }`
 */
export function classDimensionScope(
  currentUser?: CurrentUser,
): ClassDimensionScope {
  if (hasGlobalAccess(currentUser)) {
    return {};
  }

  const scope = getRoleScope(currentUser);

  if (scope === ROLE_SCOPES.CLASS) {
    const classId = getClassId(currentUser);
    return classId ? { classId } : {};
  }

  if (scope === ROLE_SCOPES.CAMPUS) {
    const campusId = getCampusId(currentUser);
    if (!campusId) {
      return {};
    }
    if (!UUID_RE.test(campusId)) {
      throw new ForbiddenError();
    }
    return {
      classId: {
        [Op.in]: literal(
          `(SELECT "id" FROM "classes" WHERE "campusId" = '${campusId}' AND "deletedAt" IS NULL)`,
        ),
      },
    };
  }

  if (scope === ROLE_SCOPES.SCHOOL) {
    const schoolId = getSchoolId(currentUser);
    if (!schoolId) {
      return {};
    }
    if (!UUID_RE.test(schoolId)) {
      throw new ForbiddenError();
    }
    return {
      classId: {
        [Op.in]: literal(
          `(SELECT "c"."id" FROM "classes" "c" JOIN "campuses" "cm" ON "cm"."id" = "c"."campusId" WHERE "cm"."schoolId" = '${schoolId}' AND "c"."deletedAt" IS NULL AND "cm"."deletedAt" IS NULL)`,
        ),
      },
    };
  }

  // organization / external / guest: no class-dimension narrowing here.
  return {};
}
