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 { RunsService } from '../runs/runs.service';
import { InspectionTaskEntity } from '../tasks/inspection-task.entity';

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

  constructor(
    @InjectRepository(InspectionTaskEntity)
    private readonly taskRepository: Repository<InspectionTaskEntity>,
    private readonly runsService: RunsService,
  ) {}

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

  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 reloadEnabledTasks() {
    const tasks = await this.taskRepository.find({
      where: { status: 'enabled' },
    });

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

    for (const task of tasks) {
      this.registerTask(task);
    }
  }

  async syncTask(taskId: string) {
    const current = this.jobs.get(taskId);
    if (current) {
      void current.stop();
      void current.destroy();
      this.jobs.delete(taskId);
    }

    const task = await this.taskRepository.findOneBy({ id: taskId });
    if (!task || task.status !== 'enabled') {
      return;
    }

    this.registerTask(task);
  }

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

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

  private registerTask(task: InspectionTaskEntity) {
    this.validateCron(task.cronExpr);

    const job = cron.schedule(task.cronExpr, () => {
      void this.executeScheduledTask(task.id);
    });

    this.jobs.set(task.id, job);
    this.logger.log(
      `registered scheduler for task ${task.id}: ${task.cronExpr}`,
    );
  }

  private async executeScheduledTask(taskId: string) {
    if (this.runningTasks.has(taskId)) {
      this.logger.warn(
        `task ${taskId} is already running, skip this schedule tick`,
      );
      return;
    }

    this.runningTasks.add(taskId);
    try {
      await this.runsService.executeTask(taskId);
    } catch (error) {
      const message =
        error instanceof Error ? error.message : 'unknown scheduler error';
      this.logger.error(`scheduled task ${taskId} failed: ${message}`);
    } finally {
      this.runningTasks.delete(taskId);
    }
  }
}
