import type { Repository, EntityManager } from 'typeorm';
import type { ClsService } from 'nestjs-cls';
import { CollectionAnalyticsRepository } from './collection-analytics.repository';
import { BookingCashCollectionEntity } from '../../../entities/booking-cash-collection.entity';
import { CLS_TENANT_ID } from '../../../common/cls/cls.constants';

/**
 * CollectionAnalyticsRepository — SQL contract tests.
 *
 * The supplier-collection page reads this CTE. Booking delete keeps
 * cash-collection rows for audit, so the CTE must inner-join live
 * bookings or deleted bookings leak onto the list.
 */
describe('CollectionAnalyticsRepository', () => {
  const TENANT_ID = 'tenant-1';
  let manager: { query: jest.Mock };
  let repo: CollectionAnalyticsRepository;

  beforeEach(() => {
    manager = {
      query: jest.fn().mockResolvedValue([{ c: 0 }]),
    };
    const typeormRepo = {
      manager: manager as unknown as EntityManager,
    } as unknown as Repository<BookingCashCollectionEntity>;
    const cls = {
      get: jest.fn((key: string) => (key === CLS_TENANT_ID ? TENANT_ID : undefined)),
    };
    repo = new CollectionAnalyticsRepository(typeormRepo, cls as unknown as ClsService);
  });

  afterEach(() => jest.clearAllMocks());

  function sqlFromCalls(): string {
    return manager.query.mock.calls.map((c) => String(c[0])).join('\n');
  }

  it('bySupplier filters collection_kind when a tab is selected', async () => {
    await repo.bySupplier({ collection_kind: 'CASH' });
    const sql = sqlFromCalls();
    expect(sql).toContain('collection_kind = $2');
    expect(manager.query.mock.calls[0][1]).toEqual([TENANT_ID, 'CASH']);
  });

  it('bySupplier joins live bookings and scopes tenant_id as $1', async () => {
    await repo.bySupplier({});
    const sql = sqlFromCalls();
    expect(sql).toContain('INNER JOIN bookings b');
    expect(sql).toContain('b.is_deleted = false');
    expect(sql).toContain('b.tenant_id = pc.tenant_id');
    expect(sql).toContain('WHERE pc.tenant_id = $1');
    expect(manager.query.mock.calls[0][1][0]).toBe(TENANT_ID);
  });

  it('supplierCollectionDrivers joins live bookings and scopes tenant_id as $1', async () => {
    await repo.supplierCollectionDrivers({
      collected_by_type: 'SUPPLIER',
      supplier_id: '11111111-1111-1111-1111-111111111111',
    });
    const sql = sqlFromCalls();
    expect(sql).toContain('INNER JOIN bookings b');
    expect(sql).toContain('b.is_deleted = false');
    expect(sql).toContain('WHERE pc.tenant_id = $1');
    expect(manager.query.mock.calls[0][1][0]).toBe(TENANT_ID);
  });

  it('rejects when CLS has no tenant_id (cross-tenant guard)', async () => {
    const typeormRepo = {
      manager: manager as unknown as EntityManager,
    } as unknown as Repository<BookingCashCollectionEntity>;
    const emptyCls = { get: jest.fn().mockReturnValue(undefined) };
    const unscoped = new CollectionAnalyticsRepository(
      typeormRepo,
      emptyCls as unknown as ClsService,
    );
    await expect(unscoped.bySupplier({})).rejects.toThrow(/tenant_id not found in CLS/);
  });
});
