import { Injectable, Inject, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { ONLINE_SNAPSHOT_REPOSITORY, IOnlineSnapshotRepository } from '../../domain/repositories/online-snapshot.repository.interface';
import { DAILY_ACTIVE_STATS_REPOSITORY, IDailyActiveStatsRepository } from '../../domain/repositories/daily-active-stats.repository.interface';
import { TELEMETRY_EVENT_REPOSITORY, ITelemetryEventRepository } from '../../domain/repositories/telemetry-event.repository.interface';
import { PRESENCE_SERVICE, IPresenceService } from '../../domain/ports/presence.service.interface';
import { TELEMETRY_METRICS_SERVICE, ITelemetryMetricsService } from '../../domain/ports/telemetry-metrics.service.interface';

@Injectable()
export class TelemetrySchedulerService {
  private readonly logger = new Logger(TelemetrySchedulerService.name);

  constructor(
    @Inject(ONLINE_SNAPSHOT_REPOSITORY) private readonly snapshotRepo: IOnlineSnapshotRepository,
    @Inject(DAILY_ACTIVE_STATS_REPOSITORY) private readonly dauRepo: IDailyActiveStatsRepository,
    @Inject(TELEMETRY_EVENT_REPOSITORY) private readonly eventRepo: ITelemetryEventRepository,
    @Inject(PRESENCE_SERVICE) private readonly presenceService: IPresenceService,
    @Inject(TELEMETRY_METRICS_SERVICE) private readonly metrics: ITelemetryMetricsService,
  ) {}

  /** Record online snapshot every minute */
  @Cron(CronExpression.EVERY_MINUTE)
  async recordOnlineSnapshot() {
    try {
      const count = await this.presenceService.countOnline();
      this.metrics.setOnlineUsers(count);
      const snapshot = this.snapshotRepo.create({
        ts: new Date(),
        onlineCount: count,
        windowSeconds: this.presenceService.getWindowSeconds(),
      });
      await this.snapshotRepo.save(snapshot);
    } catch (err) {
      this.logger.error(`Failed to record online snapshot: ${err.message}`);
    }
  }

  /** Clean expired Redis presence data every hour */
  @Cron(CronExpression.EVERY_HOUR)
  async cleanupExpiredPresence() {
    try {
      const removed = await this.presenceService.cleanupExpired();
      if (removed > 0) {
        this.logger.log(`Cleaned up ${removed} expired presence entries`);
      }
    } catch (err) {
      this.logger.error(`Failed to cleanup presence: ${err.message}`);
    }
  }

  /** Calculate yesterday's DAU at 1:00 AM */
  @Cron('0 1 * * *')
  async calculateYesterdayDau() {
    try {
      const yesterday = new Date();
      yesterday.setDate(yesterday.getDate() - 1);
      const dayStr = yesterday.toISOString().slice(0, 10);
      await this.calculateDauForDate(dayStr);
      this.logger.log(`Calculated DAU for ${dayStr}`);
    } catch (err) {
      this.logger.error(`Failed to calculate DAU: ${err.message}`);
    }
  }

  /** Rolling DAU update every hour */
  @Cron('30 * * * *')
  async calculateTodayDauRolling() {
    try {
      const today = new Date().toISOString().slice(0, 10);
      await this.calculateDauForDate(today);
    } catch (err) {
      this.logger.error(`Failed to calculate rolling DAU: ${err.message}`);
    }
  }

  private async calculateDauForDate(dayStr: string) {
    const startTime = new Date(`${dayStr}T00:00:00Z`);
    const endTime = new Date(`${dayStr}T23:59:59.999Z`);
    const endTimePlusOne = new Date(endTime.getTime() + 1);

    const dauCount = await this.eventRepo.getDauCount(startTime, endTimePlusOne);
    const platformBreakdown = await this.eventRepo.getDauByPlatform(startTime, endTimePlusOne);
    const regionBreakdown = await this.eventRepo.getDauByRegion(startTime, endTimePlusOne);

    const dauByPlatform: Record<string, number> = {};
    for (const r of platformBreakdown) {
      dauByPlatform[r.key] = r.count;
    }

    const dauByRegion: Record<string, number> = {};
    for (const r of regionBreakdown) {
      dauByRegion[r.key] = r.count;
    }

    await this.dauRepo.upsert({
      day: dayStr,
      dauCount,
      dauByPlatform,
      dauByRegion,
      calculatedAt: new Date(),
    });

    this.metrics.setDau(dayStr, dauCount);
  }
}
