import {
  Injectable,
  Logger,
  OnModuleInit,
  OnModuleDestroy,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AlertConfig } from '../../entities/alert-config.entity';
import { AlertHistory } from '../../entities/alert-history.entity';
import { ServerStats } from '../../entities/server-stats.entity';
import { UpdateAlertConfigDto } from './dto/update-alert-config.dto';

/** Track the last time an alert of a given type fired per license, for cooldown enforcement. */
const ALERT_COOLDOWN_MS = 10 * 60 * 1000; // 10 minutes between identical alerts

@Injectable()
export class AlertsService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(AlertsService.name);
  private evaluatorInterval: ReturnType<typeof setInterval> | null = null;

  /** Map of `${licenseId}:${alertType}` → last triggered timestamp */
  private readonly cooldowns = new Map<string, number>();

  constructor(
    @InjectRepository(AlertConfig)
    private readonly alertConfigRepo: Repository<AlertConfig>,
    @InjectRepository(AlertHistory)
    private readonly alertHistoryRepo: Repository<AlertHistory>,
    @InjectRepository(ServerStats)
    private readonly serverStatsRepo: Repository<ServerStats>,
  ) {}

  // ---------------------------------------------------------------------------
  // Lifecycle hooks
  // ---------------------------------------------------------------------------

  onModuleInit() {
    // Poll every 90 seconds.
    this.evaluatorInterval = setInterval(() => {
      this.evaluateAllAlerts().catch(err =>
        this.logger.error('Alert evaluator error', err),
      );
    }, 90_000);

    this.logger.log('Alert evaluator started (90s polling interval)');
  }

  onModuleDestroy() {
    if (this.evaluatorInterval) {
      clearInterval(this.evaluatorInterval);
      this.evaluatorInterval = null;
    }
  }

  // ---------------------------------------------------------------------------
  // Alert evaluation engine
  // ---------------------------------------------------------------------------

  private async evaluateAllAlerts(): Promise<void> {
    // Load all alert configs in one query.
    const configs = await this.alertConfigRepo.find();

    if (configs.length === 0) return;

    for (const config of configs) {
      try {
        await this.evaluateForLicense(config);
      } catch (err) {
        this.logger.error(
          `Alert evaluation failed for license ${config.license_id}`,
          (err as Error).stack,
        );
      }
    }
  }

  private async evaluateForLicense(config: AlertConfig): Promise<void> {
    // Pull the most recent server_stats record for this license.
    const stats = await this.serverStatsRepo.findOne({
      where: { license_id: config.license_id },
      order: { recorded_at: 'DESC' },
    });

    if (!stats) return; // No data yet — can't evaluate.

    const now = Date.now();

    // --- FPS degradation alert ---
    if (config.fps_degradation_enabled && stats.fps > 0) {
      if (stats.fps < config.fps_threshold) {
        await this.maybeFireAlert(
          config,
          'fps_degradation',
          'warning',
          'FPS Degradation Detected',
          `Server FPS dropped to ${stats.fps.toFixed(1)}, below threshold of ${config.fps_threshold}`,
          {
            current_fps: stats.fps,
            threshold: config.fps_threshold,
            player_count: stats.player_count,
            recorded_at: stats.recorded_at,
          },
          now,
        );
      }
    }

    // --- Population drop alert ---
    // We need two data points to detect a *drop*, so we compare current vs
    // the max_players recorded 30 minutes ago (nearest sample).
    if (config.population_drop_enabled && stats.max_players > 0) {
      const thirtyMinAgo = new Date(Date.now() - 30 * 60 * 1000);
      const previousStats = await this.serverStatsRepo.findOne({
        where: { license_id: config.license_id },
        order: { recorded_at: 'DESC' },
      });

      // Use a second query to get a historical data point
      const historicalStats = await this.serverStatsRepo
        .createQueryBuilder('ss')
        .where('ss.license_id = :licenseId', { licenseId: config.license_id })
        .andWhere('ss.recorded_at <= :cutoff', { cutoff: thirtyMinAgo })
        .orderBy('ss.recorded_at', 'DESC')
        .limit(1)
        .getOne();

      if (historicalStats && historicalStats.player_count > 0) {
        const dropPercent =
          ((historicalStats.player_count - stats.player_count) /
            historicalStats.player_count) *
          100;

        if (dropPercent >= config.population_drop_threshold_percent) {
          await this.maybeFireAlert(
            config,
            'population_drop',
            'info',
            'Population Drop Detected',
            `Player count dropped ${dropPercent.toFixed(0)}% (${historicalStats.player_count} → ${stats.player_count}) over the last 30 minutes`,
            {
              previous_count: historicalStats.player_count,
              current_count: stats.player_count,
              drop_percent: Math.round(dropPercent),
              threshold_percent: config.population_drop_threshold_percent,
            },
            now,
          );
        }
      }
    }
  }

  /** Fire an alert if cooldown has expired. */
  private async maybeFireAlert(
    config: AlertConfig,
    alertType: string,
    severity: string,
    title: string,
    message: string,
    metadata: Record<string, any>,
    now: number,
  ): Promise<void> {
    const cooldownKey = `${config.license_id}:${alertType}`;
    const lastFired = this.cooldowns.get(cooldownKey) ?? 0;

    if (now - lastFired < ALERT_COOLDOWN_MS) {
      return; // Still in cooldown — skip.
    }

    this.cooldowns.set(cooldownKey, now);

    const history = this.alertHistoryRepo.create({
      license_id: config.license_id,
      alert_type: alertType,
      severity,
      title,
      message,
      metadata,
      notified_discord: config.notify_discord,
      notified_pushbullet: config.notify_pushbullet,
      notified_email: config.notify_email,
    });

    await this.alertHistoryRepo.save(history);

    this.logger.log(
      `Alert fired: [${alertType}] "${title}" for license ${config.license_id}`,
    );
  }

  // ---------------------------------------------------------------------------
  // CRUD
  // ---------------------------------------------------------------------------

  async getConfig(licenseId: string): Promise<AlertConfig> {
    let config = await this.alertConfigRepo.findOne({
      where: { license_id: licenseId },
    });

    if (!config) {
      config = this.alertConfigRepo.create({
        license_id: licenseId,
        population_drop_enabled: true,
        population_drop_threshold_percent: 30,
        fps_degradation_enabled: true,
        fps_threshold: 30,
        notify_discord: true,
        notify_pushbullet: false,
        notify_email: false,
      });
      await this.alertConfigRepo.save(config);
    }

    return config;
  }

  async updateConfig(licenseId: string, dto: UpdateAlertConfigDto): Promise<AlertConfig> {
    let config = await this.alertConfigRepo.findOne({
      where: { license_id: licenseId },
    });

    if (!config) {
      config = this.alertConfigRepo.create({
        license_id: licenseId,
        ...dto,
      });
    } else {
      Object.assign(config, dto);
      config.updated_at = new Date();
    }

    return this.alertConfigRepo.save(config);
  }

  async getHistory(licenseId: string, limit: number = 50): Promise<AlertHistory[]> {
    return this.alertHistoryRepo.find({
      where: { license_id: licenseId },
      order: { triggered_at: 'DESC' },
      take: limit,
    });
  }
}
