// src/services/scheduler.ts
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
// Licensed under the Apache License, Version 2.0

import env from "../config/env.ts";
import { CronJob } from "cron";
import type { IProjectTask, IProject } from "@gadget/api";
import { GadgetService } from "../lib/service.ts";
import PlatformService from "./platform.ts";

class SchedulerService extends GadgetService {
  private jobs: Map<string, CronJob> = new Map();
  private _activeCount: number = 0;

  get name(): string {
    return "SchedulerService";
  }
  get slug(): string {
    return "svc:scheduler";
  }

  get activeCount(): number {
    return this._activeCount;
  }

  get scheduledCount(): number {
    return this.jobs.size;
  }

  async start(): Promise<void> {
    this.log.info("started");
  }

  async stop(): Promise<void> {
    for (const [taskId, job] of this.jobs) {
      job.stop();
      this.log.info("stopped cron job", { taskId });
    }
    this.jobs.clear();
    this.log.info("stopped");
  }

  /**
   * Schedules a task by creating a CronJob.
   * Key: task._id (string)
   */
  scheduleTask(task: IProjectTask, project: IProject): void {
    // If already scheduled, unschedule first
    const taskId = String(task._id);
    if (this.jobs.has(taskId)) {
      this.unscheduleTask(taskId);
    }

    try {
      const job = new CronJob(
        task.crontab, // cron expression
        async () => {
          await this.executeTask(task, project);
        },
        null, // onComplete
        true, // start
        env.timezone, // timeZone
      );

      this.jobs.set(taskId, job);
      this.log.info("task scheduled", {
        taskId,
        taskName: task.name,
        crontab: task.crontab,
        projectSlug: project.slug,
      });
    } catch (error) {
      const err = error as Error;
      this.log.error("failed to schedule task", {
        taskId,
        crontab: task.crontab,
        error: err.message,
      });
    }
  }

  /**
   * Removes a scheduled task.
   */
  unscheduleTask(taskId: string): void {
    const job = this.jobs.get(taskId);
    if (job) {
      job.stop();
      this.jobs.delete(taskId);
      this.log.info("task unscheduled", { taskId });
    }
  }

  /**
   * Execute a task with concurrency control.
   * Delegates to PlatformService.executeTask() which handles
   * the full headless IDE pipeline.
   */
  private async executeTask(task: IProjectTask, project: IProject): Promise<void> {
    // Check concurrency
    if (this._activeCount >= env.concurrency) {
      this.log.warn("task skipped: concurrency limit reached", {
        taskId: task._id,
        taskName: task.name,
        activeCount: this._activeCount,
        maxConcurrency: env.concurrency,
      });
      return;
    }

    this._activeCount++;
    try {
      this.log.info("executing task", {
        taskId: task._id,
        taskName: task.name,
        projectSlug: project.slug,
        activeCount: this._activeCount,
      });

      await PlatformService.executeTask(task, project);
    } catch (error) {
      const err = error as Error;
      this.log.error("task execution failed", {
        taskId: task._id,
        error: err.message,
      });
    } finally {
      this._activeCount--;
    }
  }
}

export default new SchedulerService();
