import { createLogger } from "../utils/logger/index.js";
import { init_logger } from "../utils/logger.js";
import { init_paths, resolveCronJobsPath } from "../config/paths.js";
import { DefaultJobExecutor } from "./executor.js";
import { AddJobRequestSchema, UpdateJobRequestSchema } from "./validation.js";
import { CronPersistence } from "./persistence.js";
import { CronRunLogStore } from "./run-log-store.js";
import nodeCron from "node-cron";
import { CronExpressionParser } from "cron-parser";
import { v4 } from "uuid";
//#region src/cron/service.ts
init_paths();
init_logger();
const log = createLogger("CronService");
var CronService = class {
	persistence;
	executor;
	runLogStore;
	tasks = /* @__PURE__ */ new Map();
	initialized = false;
	agentService = null;
	messageBus = null;
	constructor(config) {
		const filePath = config?.filePath || resolveCronJobsPath();
		this.persistence = new CronPersistence(filePath);
		this.executor = new DefaultJobExecutor();
		this.runLogStore = new CronRunLogStore(config?.runsDir);
		this.executor.setRunLogStore(this.runLogStore);
		if (config?.agentService || config?.messageBus) this.setDeps({
			agentService: config.agentService,
			messageBus: config.messageBus
		});
	}
	/**
	* Set dependencies for the executor
	*/
	setDeps(deps) {
		this.agentService = deps.agentService;
		this.messageBus = deps.messageBus;
		this.executor.setDeps(deps);
		log.debug("CronService dependencies set");
	}
	/**
	* Initialize the service and load all jobs
	*/
	async initialize() {
		if (this.initialized) return;
		await this.persistence.initialize();
		await this.loadAllJobs();
		this.initialized = true;
		log.debug("CronService initialized");
	}
	/**
	* Add a new job
	*/
	async addJob(schedule, options) {
		const validationResult = AddJobRequestSchema.safeParse({
			schedule,
			...options
		});
		if (!validationResult.success) {
			const errorMsg = validationResult.error?.errors.map((e) => e.message).join(", ") || "Unknown validation error";
			throw new Error(`Validation failed: ${errorMsg}`);
		}
		const data = validationResult.data;
		const id = v4().slice(0, 8);
		const now = (/* @__PURE__ */ new Date()).toISOString();
		const sessionTarget = data.sessionTarget ?? "main";
		const job = {
			id,
			name: data.name,
			schedule: data.schedule,
			enabled: true,
			timezone: data.timezone,
			maxRetries: data.maxRetries ?? 3,
			timeout: data.timeout ?? 6e4,
			created_at: now,
			updated_at: now,
			sessionTarget,
			...data.agentId ? { agentId: data.agentId } : {},
			...data.workingDirectory ? { workingDirectory: data.workingDirectory } : {},
			payload: data.payload,
			delivery: data.delivery,
			model: data.model
		};
		await this.persistence.addJob(job);
		this.scheduleJob(job);
		log.info({
			jobId: id,
			name: data.name,
			sessionTarget
		}, "Job added");
		return {
			id,
			schedule: data.schedule
		};
	}
	/**
	* List all jobs with next run time
	*/
	async listJobs() {
		return (await this.persistence.getJobs()).map((job) => {
			let nextRun;
			if (job.enabled) try {
				const options = job.timezone ? { tz: job.timezone } : void 0;
				nextRun = CronExpressionParser.parse(job.schedule, options).next().toISOString();
			} catch (err) {
				log.debug({
					jobId: job.id,
					err
				}, "Failed to parse cron for next run");
			}
			return {
				id: job.id,
				name: job.name,
				schedule: job.schedule,
				enabled: job.enabled,
				timezone: job.timezone,
				maxRetries: job.maxRetries,
				timeout: job.timeout,
				sessionTarget: job.sessionTarget,
				...job.agentId ? { agentId: job.agentId } : {},
				...job.workingDirectory ? { workingDirectory: job.workingDirectory } : {},
				payload: job.payload,
				delivery: job.delivery,
				model: job.model,
				next_run: nextRun
			};
		});
	}
	/**
	* Get a single job
	*/
	async getJob(id) {
		return this.persistence.getJob(id);
	}
	/**
	* Update a job
	*/
	async updateJob(id, updates) {
		const validationResult = UpdateJobRequestSchema.safeParse(updates);
		if (!validationResult.success) {
			const errorMsg = validationResult.error?.errors.map((e) => e.message).join(", ") || "Unknown validation error";
			throw new Error(`Validation failed: ${errorMsg}`);
		}
		const job = await this.persistence.getJob(id);
		if (!job) return false;
		const { agentId: agentIdPatch, workingDirectory: workingDirectoryPatch, ...rest } = validationResult.data;
		const patch = { ...rest };
		let clearAgentId = false;
		if (agentIdPatch === null) clearAgentId = true;
		else if (agentIdPatch !== void 0) patch.agentId = agentIdPatch;
		let clearWorkingDirectory = false;
		if (workingDirectoryPatch === null) clearWorkingDirectory = true;
		else if (workingDirectoryPatch !== void 0) patch.workingDirectory = workingDirectoryPatch;
		if (patch.schedule && patch.schedule !== job.schedule) this.cancelTask(id);
		await this.persistence.updateJob(id, patch, {
			clearAgentId,
			clearWorkingDirectory
		});
		const updated = await this.persistence.getJob(id);
		if (updated && updated.enabled) this.scheduleJob(updated);
		log.info({ jobId: id }, "Job updated");
		return true;
	}
	/**
	* Remove a job
	*/
	async removeJob(id) {
		this.cancelTask(id);
		const removed = await this.persistence.removeJob(id);
		if (removed) {
			await this.runLogStore.deleteJobRuns(id);
			log.info({ jobId: id }, "Job removed");
		}
		return removed;
	}
	/**
	* Toggle job enabled state
	*/
	async toggleJob(id, enabled) {
		if (!await this.persistence.getJob(id)) return false;
		await this.persistence.updateJob(id, { enabled });
		if (enabled) {
			const updated = await this.persistence.getJob(id);
			if (updated) this.scheduleJob(updated);
		} else this.cancelTask(id);
		log.info({
			jobId: id,
			enabled
		}, "Job toggled");
		return true;
	}
	/**
	* Execution history for one job: merged in-memory (incl. running) + persisted JSONL.
	*/
	async getJobHistory(jobId, limit) {
		const cap = Math.min(Math.max(limit ?? 10, 1), 500);
		const [disk, memory] = await Promise.all([this.runLogStore.readJobHistory(jobId, cap * 2), Promise.resolve(this.executor.getHistory(jobId, cap * 2))]);
		const byId = /* @__PURE__ */ new Map();
		for (const e of memory) byId.set(e.id, e);
		for (const e of disk) if (!byId.has(e.id)) byId.set(e.id, e);
		return [...byId.values()].sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime()).slice(0, cap);
	}
	/**
	* Latest runs across all jobs (persisted rows + optional job name).
	*/
	async getAllRunsHistory(limit) {
		const cap = Math.min(Math.max(limit ?? 50, 1), 500);
		const jobs = await this.persistence.getJobs();
		const jobNames = new Map(jobs.map((j) => [j.id, j.name]));
		return this.runLogStore.readAllRuns(cap, jobNames);
	}
	/**
	* Get service metrics
	*/
	async getMetrics() {
		const jobs = await this.persistence.getJobs();
		const enabled = jobs.filter((j) => j.enabled);
		let nextScheduledJob;
		for (const job of enabled) try {
			const options = job.timezone ? { tz: job.timezone } : void 0;
			const next = CronExpressionParser.parse(job.schedule, options).next();
			if (!nextScheduledJob || next.getTime() < nextScheduledJob.runAt.getTime()) nextScheduledJob = {
				id: job.id,
				name: job.name,
				runAt: next.toDate()
			};
		} catch {}
		return {
			totalJobs: jobs.length,
			runningJobs: this.tasks.size,
			enabledJobs: enabled.length,
			failedLastHour: 0,
			avgExecutionTime: 0,
			nextScheduledJob
		};
	}
	/**
	* Update config (hot reload)
	*/
	updateConfig(_config) {
		log.debug("Cron config updated (jobs managed separately via addJob/removeJob)");
	}
	/**
	* Health check
	*/
	async healthCheck() {
		const issues = [];
		if (!this.initialized) issues.push("Service not initialized");
		const jobs = await this.persistence.getJobs();
		for (const job of jobs) if (!nodeCron.validate(job.schedule)) issues.push(`Job ${job.id} has invalid schedule`);
		for (const job of jobs) if (job.enabled && job.sessionTarget === "isolated" && !this.agentService) {
			issues.push(`Job ${job.id} requires agent service but none configured`);
			break;
		}
		let status = "healthy";
		if (issues.length > 0) status = issues.length > 5 ? "unhealthy" : "degraded";
		return {
			status,
			issues
		};
	}
	/**
	* Run a job immediately (manual trigger)
	*/
	async runJobNow(id) {
		const job = await this.persistence.getJob(id);
		if (!job) throw new Error(`Job not found: ${id}`);
		if (!job.enabled) throw new Error(`Job is disabled: ${id}`);
		await this.executeJob(job);
	}
	/**
	* Stop all jobs and cleanup
	*/
	async stop(options) {
		const timeout = options?.timeout ?? 3e4;
		for (const [id, task] of this.tasks) {
			task.stop();
			this.executor.cancelJob(id);
		}
		this.tasks.clear();
		if (options?.waitForRunning) {
			const start = Date.now();
			while (Date.now() - start < timeout) {
				if (this.executor.getRunningExecutions().length === 0) break;
				await new Promise((r) => setTimeout(r, 100));
			}
		}
		await this.persistence.flush();
		log.info("CronService stopped");
	}
	/**
	* Load and schedule all enabled jobs
	*/
	async loadAllJobs() {
		const jobs = await this.persistence.getJobs();
		for (const job of jobs) if (job.enabled) this.scheduleJob(job);
		log.debug({ count: this.tasks.size }, "Jobs loaded");
	}
	/**
	* Schedule a job with node-cron
	*/
	scheduleJob(job) {
		this.cancelTask(job.id);
		if (!nodeCron.validate(job.schedule)) {
			log.error({
				jobId: job.id,
				schedule: job.schedule
			}, "Invalid cron expression");
			return;
		}
		const options = job.timezone ? { timezone: job.timezone } : void 0;
		const task = nodeCron.schedule(job.schedule, async () => {
			await this.executeJob(job);
		}, options);
		this.tasks.set(job.id, task);
		log.debug({
			jobId: job.id,
			schedule: job.schedule
		}, "Job scheduled");
	}
	/**
	* Execute a job with retry logic
	*/
	async executeJob(job) {
		if (this.executor.isRunning(job.id)) {
			log.warn({ jobId: job.id }, "Job already running, skipping");
			return;
		}
		const controller = new AbortController();
		try {
			await this.executor.execute(job, controller.signal);
		} catch (error) {
			log.error({
				jobId: job.id,
				err: error
			}, "Job execution failed");
		}
	}
	/**
	* Cancel a scheduled task
	*/
	cancelTask(id) {
		const task = this.tasks.get(id);
		if (task) {
			task.stop();
			this.tasks.delete(id);
			log.debug({ jobId: id }, "Task cancelled");
		}
	}
};
//#endregion
export { CronService };

//# sourceMappingURL=service.js.map