/**
 * Shared test utilities for backend unit tests.
 *
 * This module provides test data builders and mock factories for
 * consistent test setup across the codebase.
 */
import type { CurrentUser, DbApiOptions } from '@/db/api/types';
import type { CrudDbApi } from '@/services/shared/crud-service';

/**
 * Creates a test user with sensible defaults. Override any properties as needed.
 */
export function createTestUser(overrides: Partial<CurrentUser> = {}): CurrentUser {
  return {
    id: 'test-user-id',
    email: 'test@example.com',
    firstName: 'Test',
    lastName: 'User',
    organizationId: 'test-org-id',
    organizations: { id: 'test-org-id' },
    app_role: {
      name: 'teacher',
      globalAccess: false,
    },
    custom_permissions: [],
    ...overrides,
  };
}

/**
 * Creates a test user with global access (super_admin/system_admin).
 */
export function createGlobalAccessUser(
  overrides: Partial<CurrentUser> = {},
): CurrentUser {
  return createTestUser({
    app_role: {
      name: 'super_admin',
      globalAccess: true,
    },
    ...overrides,
  });
}

/**
 * Configuration for mock DbApi factory.
 * Requires default entity for type-safe mock returns.
 */
interface MockDbApiConfig<Entity> {
  defaultEntity: Entity;
}

/**
 * Generic mock factory for CrudDbApi. Returns an object with all CRUD
 * methods as stubs that can be customized per test.
 *
 * @param config Configuration with default entity value for type-safe returns
 * @param overrides Optional method overrides for specific test cases
 */
export function createMockDbApi<
  CreateData,
  UpdateData,
  ListFilter,
  BulkRow,
  Entity,
>(
  config: MockDbApiConfig<Entity>,
  overrides: Partial<CrudDbApi<CreateData, UpdateData, ListFilter, BulkRow, Entity>> = {},
): CrudDbApi<CreateData, UpdateData, ListFilter, BulkRow, Entity> & {
  calls: Record<string, unknown[][]>;
  reset: () => void;
} {
  const { defaultEntity } = config;
  const calls: Record<string, unknown[][]> = {
    create: [],
    bulkImport: [],
    update: [],
    deleteByIds: [],
    remove: [],
    findBy: [],
    findAll: [],
    findAllAutocomplete: [],
  };

  return {
    calls,
    reset() {
      for (const key of Object.keys(calls)) {
        calls[key] = [];
      }
    },

    async create(data: CreateData, options?: DbApiOptions): Promise<Entity> {
      calls.create.push([data, options]);
      if (overrides.create) {
        return overrides.create(data, options);
      }
      return defaultEntity;
    },

    async bulkImport(rows: BulkRow[], options?: DbApiOptions): Promise<Entity[]> {
      calls.bulkImport.push([rows, options]);
      if (overrides.bulkImport) {
        return overrides.bulkImport(rows, options);
      }
      return rows.map(() => defaultEntity);
    },

    async update(
      id: string,
      data: UpdateData,
      options?: DbApiOptions,
    ): Promise<Entity | null> {
      calls.update.push([id, data, options]);
      if (overrides.update) {
        return overrides.update(id, data, options);
      }
      return defaultEntity;
    },

    async deleteByIds(ids: string[], options?: DbApiOptions): Promise<unknown> {
      calls.deleteByIds.push([ids, options]);
      if (overrides.deleteByIds) {
        return overrides.deleteByIds(ids, options);
      }
      return ids.length;
    },

    async remove(id: string, options?: DbApiOptions): Promise<unknown> {
      calls.remove.push([id, options]);
      if (overrides.remove) {
        return overrides.remove(id, options);
      }
      return true;
    },

    async findBy(where: { id: string }, options?: DbApiOptions): Promise<unknown> {
      calls.findBy.push([where, options]);
      if (overrides.findBy) {
        return overrides.findBy(where, options);
      }
      return defaultEntity;
    },

    async findAll(
      filter: ListFilter,
      globalAccess: boolean,
      options?: DbApiOptions,
    ): Promise<{ rows: Entity[]; count: number }> {
      calls.findAll.push([filter, globalAccess, options]);
      if (overrides.findAll) {
        return overrides.findAll(filter, globalAccess, options);
      }
      return { rows: [], count: 0 };
    },

    async findAllAutocomplete(
      query: string | undefined,
      limit: number | undefined,
      offset: number | undefined,
      globalAccess: boolean,
      organizationId?: string,
    ): Promise<unknown> {
      calls.findAllAutocomplete.push([query, limit, offset, globalAccess, organizationId]);
      if (overrides.findAllAutocomplete) {
        return overrides.findAllAutocomplete(query, limit, offset, globalAccess, organizationId);
      }
      return [];
    },
  };
}

/**
 * Creates mock options for DbApi calls in tests.
 */
export function createMockOptions(
  overrides: Partial<DbApiOptions> = {},
): DbApiOptions {
  return {
    currentUser: createTestUser(),
    ...overrides,
  };
}

/**
 * Mock request object for controller/service tests.
 */
export function createMockRequest(overrides: Record<string, unknown> = {}) {
  return {
    headers: {
      'user-agent': 'test-agent',
    },
    ip: '127.0.0.1',
    socket: {
      remoteAddress: '127.0.0.1',
    },
    cookies: {},
    body: {},
    query: {},
    params: {},
    ...overrides,
  };
}

/**
 * Simple stub that tracks calls and returns a configurable value.
 */
export function createStub<T>(returnValue?: T) {
  const calls: unknown[][] = [];

  const stub = (...args: unknown[]) => {
    calls.push(args);
    return returnValue;
  };

  stub.calls = calls;
  stub.reset = () => {
    calls.length = 0;
  };

  return stub;
}

/**
 * Async stub variant.
 */
export function createAsyncStub<T>(returnValue?: T) {
  const calls: unknown[][] = [];

  const stub = async (...args: unknown[]) => {
    calls.push(args);
    return returnValue;
  };

  stub.calls = calls;
  stub.reset = () => {
    calls.length = 0;
  };

  return stub;
}
