import type { ObjectLiteral } from 'typeorm';
import type { ConfigService } from '@nestjs/config';
import type { Mock, Mocked } from 'vitest';

/**
 * Mock DataSource for services that use @InjectDataSource() with raw SQL.
 * Follows ISP: separate from repository mocks.
 */
export interface MockDataSource {
  query: Mock;
  getRepository: Mock;
  createQueryRunner: Mock;
}

/**
 * Creates a mock DataSource for services using raw SQL queries.
 * The query mock resolves to empty arrays by default.
 * Override per-test with mockDataSource.query.mockResolvedValueOnce(...)
 */
export function createMockDataSource(): MockDataSource {
  return {
    query: vi.fn().mockResolvedValue([]),
    getRepository: vi.fn(),
    createQueryRunner: vi.fn(),
  };
}

/**
 * Mock repository type for services using @InjectRepository().
 */
export interface MockRepository<_T extends ObjectLiteral = ObjectLiteral> {
  find: Mock;
  findOne: Mock;
  findOneBy: Mock;
  save: Mock;
  create: Mock;
  update: Mock;
  delete: Mock;
  remove: Mock;
  count: Mock;
  createQueryBuilder: Mock;
  queryBuilder: MockQueryBuilder;
}

/**
 * Creates a mock TypeORM repository with chainable query builder.
 */
export function createMockRepository<T extends ObjectLiteral>(): MockRepository<T> {
  const queryBuilder = createMockQueryBuilder();

  return {
    find: vi.fn(),
    findOne: vi.fn(),
    findOneBy: vi.fn(),
    save: vi.fn(),
    create: vi.fn(),
    update: vi.fn(),
    delete: vi.fn(),
    remove: vi.fn(),
    count: vi.fn(),
    createQueryBuilder: vi.fn().mockReturnValue(queryBuilder),
    queryBuilder,
  };
}

export interface MockQueryBuilder {
  select: Mock;
  addSelect: Mock;
  where: Mock;
  andWhere: Mock;
  orWhere: Mock;
  groupBy: Mock;
  addGroupBy: Mock;
  orderBy: Mock;
  limit: Mock;
  offset: Mock;
  getRawOne: Mock;
  getRawMany: Mock;
  getOne: Mock;
  getMany: Mock;
  getCount: Mock;
  leftJoin: Mock;
  innerJoin: Mock;
}

/**
 * Creates a mock SelectQueryBuilder with chainable methods.
 */
export function createMockQueryBuilder(): MockQueryBuilder {
  const builder: MockQueryBuilder = {
    select: vi.fn(),
    addSelect: vi.fn(),
    where: vi.fn(),
    andWhere: vi.fn(),
    orWhere: vi.fn(),
    groupBy: vi.fn(),
    addGroupBy: vi.fn(),
    orderBy: vi.fn(),
    limit: vi.fn(),
    offset: vi.fn(),
    getRawOne: vi.fn().mockResolvedValue(null),
    getRawMany: vi.fn().mockResolvedValue([]),
    getOne: vi.fn().mockResolvedValue(null),
    getMany: vi.fn().mockResolvedValue([]),
    getCount: vi.fn().mockResolvedValue(0),
    leftJoin: vi.fn(),
    innerJoin: vi.fn(),
  };

  // Make chainable methods return builder
  const chainable = [
    'select', 'addSelect', 'where', 'andWhere', 'orWhere',
    'groupBy', 'addGroupBy', 'orderBy', 'limit', 'offset',
    'leftJoin', 'innerJoin',
  ] as const;

  for (const method of chainable) {
    builder[method].mockReturnValue(builder);
  }

  return builder;
}

/**
 * Creates a mock ConfigService with configurable return values.
 */
export function createMockConfigService(
  config: Record<string, string | undefined> = {},
): Mocked<ConfigService> {
  return {
    get: vi.fn((key: string) => config[key]),
    getOrThrow: vi.fn((key: string) => {
      if (config[key] === undefined) {
        throw new Error(`Config key ${key} not found`);
      }
      return config[key];
    }),
  } as unknown as Mocked<ConfigService>;
}
