import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model, Types } from 'mongoose';
import { FeedVersionService } from '../../infrastructure/cache/feed-version.service';
import { PostsService } from '../posts/posts.service';
import { TrackEngagementDto } from './dto/track-engagement.dto';
import { EngagementEventType } from './engagement-event-type.enum';
import { EngagementEvent, EngagementEventDocument } from './schemas/engagement-event.schema';
import { PostPreference, PostPreferenceDocument } from './schemas/post-preference.schema';

@Injectable()
export class EngagementService {
  constructor(
    @InjectModel(EngagementEvent.name) private readonly eventModel: Model<EngagementEventDocument>,
    @InjectModel(PostPreference.name) private readonly preferenceModel: Model<PostPreferenceDocument>,
    private readonly postsService: PostsService,
    private readonly feedVersionService: FeedVersionService,
  ) {}

  async track(userId: string, dto: TrackEngagementDto) {
    if (dto.type === EngagementEventType.NOT_INTERESTED) {
      return this.setNotInterested(userId, dto.postId, true);
    }
    await this.postsService.findById(dto.postId, userId);
    await this.eventModel.create({
      userId: new Types.ObjectId(userId),
      postId: new Types.ObjectId(dto.postId),
      type: dto.type,
      watchTimeMs: dto.watchTimeMs ?? 0,
      progressPercent: dto.progressPercent ?? 0,
      sessionId: dto.sessionId?.trim() ?? '',
      occurredAt: new Date(),
    });
    return { accepted: true, postId: dto.postId, type: dto.type };
  }

  async trackBatch(userId: string, events: TrackEngagementDto[]) {
    const results = [];
    for (const event of events) results.push(await this.track(userId, event));
    return { accepted: results.length, items: results };
  }

  async setNotInterested(userId: string, postId: string, value: boolean) {
    if (value) {
      await this.postsService.findById(postId, userId);
      await this.preferenceModel.updateOne(
        { userId: new Types.ObjectId(userId), postId: new Types.ObjectId(postId) },
        { $set: { notInterested: true } },
        { upsert: true },
      );
      await this.eventModel.create({
        userId: new Types.ObjectId(userId), postId: new Types.ObjectId(postId),
        type: EngagementEventType.NOT_INTERESTED, occurredAt: new Date(),
      });
    } else {
      await this.preferenceModel.deleteOne({
        userId: new Types.ObjectId(userId), postId: new Types.ObjectId(postId),
      });
    }
    await this.feedVersionService.bumpUserVersion(userId);
    return { postId, notInterested: value };
  }

  async getNotInterestedPostIds(userId: string): Promise<string[]> {
    const rows = await this.preferenceModel
      .find({ userId: new Types.ObjectId(userId), notInterested: true })
      .select({ postId: 1 }).lean().exec();
    return rows.map((row) => row.postId.toString());
  }

  async getRankingSignals(userId: string) {
    const since = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000);
    const rows = await this.eventModel.aggregate<{
      _id: Types.ObjectId; watchMs: number; completions: number; rewatches: number; profileOpens: number;
    }>([
      { $match: { userId: new Types.ObjectId(userId), occurredAt: { $gte: since } } },
      { $group: {
        _id: '$postId', watchMs: { $sum: '$watchTimeMs' },
        completions: { $sum: { $cond: [{ $eq: ['$type', EngagementEventType.COMPLETE] }, 1, 0] } },
        rewatches: { $sum: { $cond: [{ $eq: ['$type', EngagementEventType.REWATCH] }, 1, 0] } },
        profileOpens: { $sum: { $cond: [{ $eq: ['$type', EngagementEventType.PROFILE_OPEN] }, 1, 0] } },
      } },
      { $sort: { watchMs: -1 } }, { $limit: 500 },
    ]).exec();
    return new Map(rows.map((row) => [row._id.toString(), row]));
  }

  async getMySummary(userId: string) {
    const since = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
    const byType = await this.eventModel.aggregate<{ _id: EngagementEventType; count: number; watchTimeMs: number }>([
      { $match: { userId: new Types.ObjectId(userId), occurredAt: { $gte: since } } },
      { $group: { _id: '$type', count: { $sum: 1 }, watchTimeMs: { $sum: '$watchTimeMs' } } },
    ]).exec();
    return { periodDays: 30, byType: Object.fromEntries(byType.map((row) => [row._id, { count: row.count, watchTimeMs: row.watchTimeMs }])) };
  }
}
