import {
  BadRequestException,
  Injectable,
  Logger,
  OnModuleDestroy,
  OnModuleInit,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import cron, { ScheduledTask } from 'node-cron';
import { Repository } from 'typeorm';
import { MonitorTargetEntity } from './monitor-target.entity';
import { MonitoringService } from './monitoring.service';

@Injectable()
export class MonitoringSchedulerService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(MonitoringSchedulerService.name);
  private readonly jobs = new Map<string, ScheduledTask>();
  private readonly runningTargets = new Set<string>();

  constructor(
    @InjectRepository(MonitorTargetEntity)
    private readonly targetRepository: Repository<MonitorTargetEntity>,
    private readonly monitoringService: MonitoringService,
  ) {}

  async onModuleInit() {
    await this.reloadEnabledTargets();
  }

  onModuleDestroy() {
    for (const job of this.jobs.values()) {
      void job.stop();
      void job.destroy();
    }
    this.jobs.clear();
  }

  validateCron(cronExpr: string) {
    if (!cron.validate(cronExpr)) {
      throw new BadRequestException('cron_expr 不合法，无法注册监测任务。');
    }
  }

  async reloadEnabledTargets() {
    const targets = await this.targetRepository.find({
      where: { status: 'enabled' },
    });

    for (const job of this.jobs.values()) {
      void job.stop();
      void job.destroy();
    }
    this.jobs.clear();

    for (const target of targets) {
      this.registerTarget(target);
    }
  }

  async syncTarget(targetId: string) {
    await this.removeTarget(targetId);

    const target = await this.targetRepository.findOneBy({ id: targetId });
    if (!target || target.status !== 'enabled') {
      return;
    }

    this.registerTarget(target);
  }

  async removeTarget(targetId: string) {
    const current = this.jobs.get(targetId);
    if (!current) {
      return;
    }

    void current.stop();
    void current.destroy();
    this.jobs.delete(targetId);
  }

  private registerTarget(target: MonitorTargetEntity) {
    this.validateCron(target.cronExpr);
    const job = cron.schedule(target.cronExpr, () => {
      void this.executeScheduledTarget(target.id);
    });

    this.jobs.set(target.id, job);
    this.logger.log(`registered monitor target ${target.id}: ${target.cronExpr}`);
  }

  private async executeScheduledTarget(targetId: string) {
    if (this.runningTargets.has(targetId)) {
      this.logger.warn(
        `monitor target ${targetId} is already running, skip this schedule tick`,
      );
      return;
    }

    this.runningTargets.add(targetId);
    try {
      await this.monitoringService.executeTarget(targetId);
    } catch (error) {
      const message =
        error instanceof Error ? error.message : 'unknown scheduler error';
      this.logger.error(`scheduled monitor target ${targetId} failed: ${message}`);
    } finally {
      this.runningTargets.delete(targetId);
    }
  }
}
