import { Injectable, OnModuleInit } from '@nestjs/common';
import { InjectConnection } from '@nestjs/mongoose';
import { Connection, Types } from 'mongoose';
import { AppQueueService } from '../queue/app-queue.service';

const JOB_NAME = 'maintenance.reconcile-post-counters';

@Injectable()
export class CounterReconciliationService implements OnModuleInit {
  constructor(
    @InjectConnection() private readonly connection: Connection,
    private readonly queue: AppQueueService,
  ) {}

  onModuleInit(): void {
    this.queue.registerProcessor(JOB_NAME, async () => { await this.reconcile(false); });
  }

  async enqueue(): Promise<{ queued: true }> {
    await this.queue.enqueue(JOB_NAME, {});
    return { queued: true };
  }

  async reconcile(dryRun = true): Promise<{ dryRun: boolean; scannedPosts: number; updates: number }> {
    const posts = await this.connection.collection('posts').find(
      { isDeleted: { $ne: true } },
      { projection: { _id: 1 } },
    ).toArray();
    const [likes, comments, saves, shares] = await Promise.all([
      this.countByPost('likes', { targetType: 'post' }, '$targetId'),
      this.countByPost('comments', { isDeleted: { $ne: true } }, '$postId'),
      this.countByPost('saves', {}, '$postId'),
      this.countByPost('postshares', { isDeleted: { $ne: true } }, '$postId'),
    ]);
    const maps = [likes, comments, saves, shares].map((rows) =>
      new Map(rows.map((row) => [row._id.toString(), row.count])),
    );
    const operations = posts.map((post) => ({
      updateOne: {
        filter: { _id: post._id },
        update: { $set: {
          likesCount: maps[0].get(post._id.toString()) ?? 0,
          commentsCount: maps[1].get(post._id.toString()) ?? 0,
          savesCount: maps[2].get(post._id.toString()) ?? 0,
          shareCount: maps[3].get(post._id.toString()) ?? 0,
        } },
      },
    }));
    if (!dryRun) {
      for (let offset = 0; offset < operations.length; offset += 500) {
        await this.connection.collection('posts').bulkWrite(operations.slice(offset, offset + 500), { ordered: false });
      }
    }
    return { dryRun, scannedPosts: posts.length, updates: operations.length };
  }

  private countByPost(collection: string, match: Record<string, unknown>, id: string) {
    return this.connection.collection(collection).aggregate<{ _id: Types.ObjectId; count: number }>([
      { $match: match },
      { $group: { _id: id, count: { $sum: 1 } } },
      { $match: { _id: { $type: 'objectId' } } },
    ]).toArray();
  }
}
