import { afterEach, mock, test } from 'node:test';
import assert from 'node:assert/strict';
import { inspect } from 'node:util';
import { Op, type FindAndCountOptions } from 'sequelize';

import db from '@/db/models';
import UsersDBApi from '@/db/api/users';

afterEach(() => {
  mock.restoreAll();
});

test('UsersDBApi query search uses correlated subqueries for related names', async () => {
  let capturedOptions: FindAndCountOptions | null = null;

  mock.method(
    db.users,
    'findAndCountAll',
    (async (options: FindAndCountOptions) => {
      capturedOptions = options;
      return { rows: [], count: 0 };
    }) as unknown as typeof db.users.findAndCountAll,
  );

  await UsersDBApi.findAll({ query: 'John', limit: 10, page: 0 }, true, {});

  assert.ok(capturedOptions, 'expected findAndCountAll to be called');
  const options = capturedOptions as FindAndCountOptions;
  const where = options.where as Record<string | symbol, unknown>;
  const andConditions = where[Op.and];

  assert.ok(Array.isArray(andConditions), 'expected query search conditions in Op.and');
  const serializedWhere = inspect(where, { depth: 10 });

  assert.ok(
    serializedWhere.includes('FROM "organizations"'),
    'expected organization name search to use a correlated subquery',
  );
  assert.ok(
    serializedWhere.includes('FROM "schools"'),
    'expected school name search to use a correlated subquery',
  );
  assert.ok(
    serializedWhere.includes('FROM "campuses"'),
    'expected campus name search to use a correlated subquery',
  );
  assert.ok(
    serializedWhere.includes('FROM "classes"'),
    'expected class name search to use a correlated subquery',
  );
  assert.ok(
    serializedWhere.includes('FROM "roles"'),
    'expected role name search to use a correlated subquery',
  );
  assert.equal(
    serializedWhere.includes('lower("school"."name")')
      || serializedWhere.includes('lower("campus"."name")')
      || serializedWhere.includes('lower("class"."name")')
      || serializedWhere.includes('lower("app_role"."name")'),
    false,
    'expected no direct joined-alias name references in the where clause',
  );
});

test('UsersDBApi ignores removed user filters', async () => {
  let capturedOptions: FindAndCountOptions | null = null;

  mock.method(
    db.users,
    'findAndCountAll',
    (async (options: FindAndCountOptions) => {
      capturedOptions = options;
      return { rows: [], count: 0 };
    }) as unknown as typeof db.users.findAndCountAll,
  );

  const removedFilter = {
    active: 'true',
    password: 'secret',
    emailVerificationToken: 'verify-token',
    passwordResetToken: 'reset-token',
    disabled: 'false',
    limit: 10,
    page: 0,
  };

  await UsersDBApi.findAll(removedFilter, true, {});

  assert.ok(capturedOptions, 'expected findAndCountAll to be called');
  const options = capturedOptions as FindAndCountOptions;
  const where = options.where as Record<string | symbol, unknown>;
  const serializedWhere = inspect(where, { depth: 10 });

  assert.equal(where.disabled, false);
  assert.equal(serializedWhere.includes('active'), false);
  assert.equal(serializedWhere.includes('password'), false);
  assert.equal(serializedWhere.includes('emailVerificationToken'), false);
  assert.equal(serializedWhere.includes('passwordResetToken'), false);
});

test('UsersDBApi campus filter includes class-scoped users inside that campus', async () => {
  let capturedOptions: FindAndCountOptions | null = null;

  mock.method(
    db.users,
    'findAndCountAll',
    (async (options: FindAndCountOptions) => {
      capturedOptions = options;
      return { rows: [], count: 0 };
    }) as unknown as typeof db.users.findAndCountAll,
  );

  await UsersDBApi.findAll(
    { campusId: '00000000-0000-4000-8000-000000000001', limit: 10, page: 0 },
    true,
    {},
  );

  assert.ok(capturedOptions, 'expected findAndCountAll to be called');
  const options = capturedOptions as FindAndCountOptions;
  const where = options.where as Record<string | symbol, unknown>;
  const serializedWhere = inspect(where, { depth: 10 });

  assert.ok(
    serializedWhere.includes('FROM "classes" WHERE "campusId"'),
    'expected campus filtering to include class-scoped users',
  );
});

test('UsersDBApi class filter includes enrolled students', async () => {
  let capturedOptions: FindAndCountOptions | null = null;

  mock.method(
    db.users,
    'findAndCountAll',
    (async (options: FindAndCountOptions) => {
      capturedOptions = options;
      return { rows: [], count: 0 };
    }) as unknown as typeof db.users.findAndCountAll,
  );

  await UsersDBApi.findAll(
    { classId: '00000000-0000-4000-8000-000000000002', limit: 10, page: 0 },
    true,
    {},
  );

  assert.ok(capturedOptions, 'expected findAndCountAll to be called');
  const options = capturedOptions as FindAndCountOptions;
  const where = options.where as Record<string | symbol, unknown>;
  const serializedWhere = inspect(where, { depth: 10 });

  assert.ok(
    serializedWhere.includes('FROM "class_enrollments"'),
    'expected class filtering to include enrolled students',
  );
});
