"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var SchedulerServiceImpl_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SchedulerServiceImpl = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("typeorm");
const schedule_1 = require("@nestjs/schedule");
const solid_registry_1 = require("../../helpers/solid-registry");
const scheduled_job_repository_1 = require("../../repository/scheduled-job.repository");
const cron_parser_1 = require("cron-parser");
let SchedulerServiceImpl = SchedulerServiceImpl_1 = class SchedulerServiceImpl {
    constructor(scheduledJobRepo, solidRegistry) {
        this.scheduledJobRepo = scheduledJobRepo;
        this.solidRegistry = solidRegistry;
        this.logger = new common_1.Logger(SchedulerServiceImpl_1.name);
        this.runningJobs = new Set();
    }
    async runScheduledJobs() {
        const solidSchedulerEnabled = process.env.SOLID_SCHEDULER_ENABLED || "true";
        if (solidSchedulerEnabled.toLowerCase() !== "true") {
            this.logger.debug('Solid scheduler is disabled via environment variable');
            return;
        }
        const solidCliRunning = process.env.SOLID_CLI_RUNNING || "false";
        if (solidCliRunning === "true") {
            return;
        }
        const jobsRegexToEnable = (process.env.SOLID_SCHEDULER_JOBS_REGEX_TO_ENABLE || '').trim();
        let jobsRegex = null;
        if (jobsRegexToEnable && jobsRegexToEnable !== "all") {
            try {
                jobsRegex = new RegExp(jobsRegexToEnable);
            }
            catch (error) {
                this.logger.error(`Invalid SOLID_SCHEDULER_JOBS_REGEX_TO_ENABLE regex "${jobsRegexToEnable}". Scheduler loop will skip this run.`);
                return;
            }
        }
        const now = new Date();
        const dueJobs = await this.scheduledJobRepo.find({
            where: [
                {
                    isActive: true,
                    nextRunAt: (0, typeorm_1.LessThanOrEqual)(now),
                },
                {
                    isActive: true,
                    nextRunAt: (0, typeorm_1.IsNull)(),
                },
            ],
        });
        for (const job of dueJobs) {
            const jobKey = String(job.id ?? job.scheduleName ?? job.job);
            const jobName = String(job.job ?? '');
            if (jobsRegex && !jobsRegex.test(jobName)) {
                this.logger.log(`[${now.getTime()}]: scheduler service skipping job ${jobName} because it does not match SOLID_SCHEDULER_JOBS_REGEX_TO_ENABLE=${jobsRegexToEnable}`);
                continue;
            }
            if (this.runningJobs.has(jobKey)) {
                this.logger.log(`[${now.getTime()}]: scheduler service skipping job ${job.job} because a run is already in progress`);
                continue;
            }
            if (!this.shouldRunNow(job, now)) {
                this.logger.log(`[${now.getTime()}]: scheduler service skipping job ${job.job}`);
                continue;
            }
            const handler = this.solidRegistry.getScheduledJobProviderInstance(job.job);
            if (!handler) {
                this.logger.warn(`[${now.getTime()}]: scheduler service skipping because job handler not found: ${job.job}`);
                continue;
            }
            this.runningJobs.add(jobKey);
            this.logger.log(`[${now.getTime()}]: scheduler service attempting to run job ${job.job}`);
            try {
                await handler.execute(job);
                job.isActive = true;
                const finishedAt = new Date();
                job.lastRunAt = finishedAt;
                job.nextRunAt = this.computeNextRunAt(job, finishedAt);
                this.logger.log(`[${now.getTime()}]: scheduler service coomputed next run for ${job.job} as ${job.nextRunAt}`);
                await this.scheduledJobRepo.save(job);
                this.logger.log(`[${now.getTime()}]: scheduler service finished running job: ${job.job}`);
            }
            catch (err) {
                this.logger.error(`[${now.getTime()}]: scheduler service failed to run job ${job.job}`, err.stack);
            }
            finally {
                this.runningJobs.delete(jobKey);
            }
        }
    }
    shouldRunNow(job, now) {
        const today = new Date(now);
        today.setHours(0, 0, 0, 0);
        const timeNow = this.toHHMM(now);
        const startDate = this.toDateOnly(job.startDate);
        const endDate = this.toDateOnly(job.endDate);
        if (startDate && today < startDate)
            return false;
        if (endDate && today > endDate)
            return false;
        const jobStart = this.toHHMM(job.startTime);
        const jobEnd = this.toHHMM(job.endTime);
        if (jobStart && timeNow < jobStart)
            return false;
        if (jobEnd && timeNow > jobEnd)
            return false;
        if (job.frequency.toLowerCase() === 'custom') {
            return true;
        }
        if (job.frequency.toLowerCase() === 'weekly' && job.dayOfWeek) {
            const todayName = now.toLocaleString('en-US', { weekday: 'long' });
            const days = this.parseDayOfWeek(job.dayOfWeek);
            if (!days.includes(todayName))
                return false;
        }
        if (job.frequency.toLowerCase() === 'monthly' && job.dayOfMonth) {
            const dom = now.getDate();
            if (dom !== job.dayOfMonth)
                return false;
        }
        return true;
    }
    parseDayOfWeek(dayOfWeek) {
        try {
            const parsed = JSON.parse(dayOfWeek);
            return Array.isArray(parsed) ? parsed : [];
        }
        catch (error) {
            this.logger.warn(`Invalid dayOfWeek JSON '${dayOfWeek}'`, error);
            return [];
        }
    }
    toDateOnly(value) {
        if (!value)
            return null;
        if (value instanceof Date) {
            const d = new Date(value);
            d.setHours(0, 0, 0, 0);
            return d;
        }
        const parsed = new Date(value);
        if (Number.isNaN(parsed.getTime()))
            return null;
        parsed.setHours(0, 0, 0, 0);
        return parsed;
    }
    toHHMM(value) {
        if (!value)
            return null;
        if (value instanceof Date) {
            return value.toTimeString().slice(0, 5);
        }
        if (typeof value === 'string') {
            const match = value.match(/^(\d{2}):(\d{2})/);
            if (match)
                return `${match[1]}:${match[2]}`;
            const parsed = new Date(value);
            if (!Number.isNaN(parsed.getTime())) {
                return parsed.toTimeString().slice(0, 5);
            }
        }
        return null;
    }
    computeNextRunForCustomCron(job, from) {
        const base = new Date(from);
        if (!job.cronExpression) {
            this.logger.error(`Custom frequency requires cronExpression for job ${job.scheduleName}`);
            return new Date(base.getTime() + 24 * 60 * 60 * 1000);
        }
        try {
            const interval = cron_parser_1.CronExpressionParser.parse(job.cronExpression, {
                currentDate: from,
                tz: 'UTC'
            });
            const nextRun = interval.next().toDate();
            const runAfterNext = interval.next().toDate();
            if (runAfterNext.getTime() - nextRun.getTime() < 60000) {
                throw new Error('Cron expression interval must be at least 1 minute');
            }
            this.logger.log(`Custom cron '${job.cronExpression}' next run: ${nextRun}`);
            return nextRun;
        }
        catch (error) {
            this.logger.error(`Invalid cron expression for job ${job.scheduleName}: ${job.cronExpression}. Reason: ${error.message}`);
            return new Date(base.getTime() + 24 * 60 * 60 * 1000);
        }
    }
    computeNextRunAt(job, from) {
        const base = new Date(from);
        switch (job.frequency.toLowerCase()) {
            case 'every minute':
                return new Date(base.getTime() + 1 * 60 * 1000);
            case 'hourly':
                return new Date(base.getTime() + 60 * 60 * 1000);
            case 'daily':
                return new Date(base.getTime() + 24 * 60 * 60 * 1000);
            case 'weekly':
                return new Date(base.getTime() + 7 * 24 * 60 * 60 * 1000);
            case 'monthly':
                const next = new Date(base);
                next.setMonth(base.getMonth() + 1);
                return next;
            case 'custom':
                return this.computeNextRunForCustomCron(job, from);
            default:
                return new Date(base.getTime() + 24 * 60 * 60 * 1000);
        }
    }
};
exports.SchedulerServiceImpl = SchedulerServiceImpl;
__decorate([
    (0, schedule_1.Cron)(schedule_1.CronExpression.EVERY_MINUTE),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], SchedulerServiceImpl.prototype, "runScheduledJobs", null);
exports.SchedulerServiceImpl = SchedulerServiceImpl = SchedulerServiceImpl_1 = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [scheduled_job_repository_1.ScheduledJobRepository,
        solid_registry_1.SolidRegistry])
], SchedulerServiceImpl);
//# sourceMappingURL=scheduler.service.js.map