import { Types } from 'mongoose';
import { AppQueueService } from '../queue/app-queue.service';
import { CounterReconciliationService } from './counter-reconciliation.service';
import { MaintenanceController } from './maintenance.controller';

describe('CounterReconciliationService', () => {
  const postOne = new Types.ObjectId();
  const postTwo = new Types.ObjectId();
  let rows: Record<string, Array<{ _id: Types.ObjectId; count: number }>>;
  let postsBulkWrite: jest.Mock;
  let collection: jest.Mock;
  const queue = { registerProcessor: jest.fn(), enqueue: jest.fn() } as unknown as AppQueueService;

  beforeEach(() => {
    jest.clearAllMocks();
    rows = {
      likes: [{ _id: postOne, count: 3 }],
      comments: [{ _id: postOne, count: 2 }],
      saves: [{ _id: postTwo, count: 5 }],
      postshares: [{ _id: postOne, count: 1 }],
    };
    postsBulkWrite = jest.fn();
    collection = jest.fn((name: string) =>
      name === 'posts'
        ? {
            find: jest.fn(() => ({ toArray: jest.fn().mockResolvedValue([{ _id: postOne }, { _id: postTwo }]) })),
            bulkWrite: postsBulkWrite,
          }
        : { aggregate: jest.fn(() => ({ toArray: jest.fn().mockResolvedValue(rows[name]) })) },
    );
  });

  it('registers and enqueues reconciliation jobs', async () => {
    const service = new CounterReconciliationService({ collection } as never, queue);
    service.onModuleInit();
    expect(queue.registerProcessor).toHaveBeenCalledWith(
      'maintenance.reconcile-post-counters',
      expect.any(Function),
    );
    const callback = (queue.registerProcessor as jest.Mock).mock.calls[0][1] as () => Promise<void>;
    const reconcile = jest.spyOn(service, 'reconcile').mockResolvedValue({ dryRun: false, scannedPosts: 0, updates: 0 });
    await callback();
    expect(reconcile).toHaveBeenCalledWith(false);
    await expect(service.enqueue()).resolves.toEqual({ queued: true });
    expect(queue.enqueue).toHaveBeenCalledWith('maintenance.reconcile-post-counters', {});
  });

  it('computes every counter without writes during a dry run', async () => {
    const service = new CounterReconciliationService({ collection } as never, queue);
    await expect(service.reconcile()).resolves.toEqual({ dryRun: true, scannedPosts: 2, updates: 2 });
    expect(postsBulkWrite).not.toHaveBeenCalled();
    const likesAggregate = collection.mock.results.find((result) => result.value.aggregate)?.value.aggregate;
    expect(likesAggregate).toHaveBeenCalledWith([
      { $match: { targetType: 'post' } },
      { $group: { _id: '$targetId', count: { $sum: 1 } } },
      { $match: { _id: { $type: 'objectId' } } },
    ]);
  });

  it('writes reconciled values including zeros', async () => {
    const service = new CounterReconciliationService({ collection } as never, queue);
    await service.reconcile(false);
    expect(postsBulkWrite).toHaveBeenCalledWith(
      [
        expect.objectContaining({ updateOne: expect.objectContaining({ update: { $set: { likesCount: 3, commentsCount: 2, savesCount: 0, shareCount: 1 } } }) }),
        expect.objectContaining({ updateOne: expect.objectContaining({ update: { $set: { likesCount: 0, commentsCount: 0, savesCount: 5, shareCount: 0 } } }) }),
      ],
      { ordered: false },
    );
  });

  it('controller executes only on an explicit true flag', async () => {
    const counters = { enqueue: jest.fn().mockResolvedValue({ queued: true }), reconcile: jest.fn().mockResolvedValue({ dryRun: true }) };
    const controller = new MaintenanceController(counters as never);
    await controller.reconcile('true');
    await controller.reconcile('TRUE');
    expect(counters.enqueue).toHaveBeenCalledTimes(1);
    expect(counters.reconcile).toHaveBeenCalledWith(true);
  });
});
