"use strict";
/**
 * Cron Job Service - Manages PM2 processes with cron_restart
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.CronJobService = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const uuid_1 = require("uuid");
const pm2_connection_1 = require("../utils/pm2-connection");
const cron_parser_1 = require("cron-parser");
const CRON_CONFIG_FILE = path.join(__dirname, '../config/cron-jobs.json');
const CRON_SCRIPTS_DIR = path.join(__dirname, '../config/cron-scripts');
class CronJobService {
    constructor() {
        this.ensureConfigFile();
    }
    static getInstance() {
        if (!CronJobService.instance) {
            CronJobService.instance = new CronJobService();
        }
        return CronJobService.instance;
    }
    ensureConfigFile() {
        const dir = path.dirname(CRON_CONFIG_FILE);
        if (!fs.existsSync(dir)) {
            fs.mkdirSync(dir, { recursive: true });
        }
        if (!fs.existsSync(CRON_CONFIG_FILE)) {
            fs.writeFileSync(CRON_CONFIG_FILE, JSON.stringify([], null, 2));
        }
        // Ensure scripts directory exists for inline scripts
        if (!fs.existsSync(CRON_SCRIPTS_DIR)) {
            fs.mkdirSync(CRON_SCRIPTS_DIR, { recursive: true });
        }
    }
    readConfig() {
        try {
            const data = fs.readFileSync(CRON_CONFIG_FILE, 'utf-8');
            return JSON.parse(data);
        }
        catch (error) {
            console.error('Error reading cron config:', error);
            return [];
        }
    }
    writeConfig(configs) {
        fs.writeFileSync(CRON_CONFIG_FILE, JSON.stringify(configs, null, 2));
    }
    /**
     * Validate cron expression
     */
    validateCronExpression(expression) {
        try {
            const interval = cron_parser_1.CronExpressionParser.parse(expression);
            return {
                valid: true,
                nextRun: interval.next().toDate()
            };
        }
        catch (error) {
            return {
                valid: false,
                error: error.message
            };
        }
    }
    /**
     * Get human-readable description of cron expression
     */
    getCronDescription(expression) {
        try {
            const parts = expression.split(' ');
            if (parts.length !== 5) {
                return 'Invalid cron expression';
            }
            const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
            // Simple descriptions for common patterns
            if (expression === '* * * * *')
                return 'Every minute';
            if (expression === '0 * * * *')
                return 'Every hour';
            if (expression === '0 0 * * *')
                return 'Daily at midnight';
            if (expression === '0 0 * * 0')
                return 'Weekly on Sunday at midnight';
            if (expression === '0 0 1 * *')
                return 'Monthly on the 1st at midnight';
            let desc = 'At ';
            if (minute === '*')
                desc += 'every minute';
            else
                desc += `minute ${minute}`;
            if (hour !== '*')
                desc += ` past hour ${hour}`;
            if (dayOfMonth !== '*')
                desc += ` on day ${dayOfMonth}`;
            if (month !== '*')
                desc += ` in month ${month}`;
            if (dayOfWeek !== '*')
                desc += ` on day ${dayOfWeek} of week`;
            return desc;
        }
        catch (error) {
            return 'Invalid expression';
        }
    }
    /**
     * Get script path for inline scripts (creates temp file)
     */
    getScriptPath(config) {
        if (config.scriptMode === 'file') {
            return config.scriptPath;
        }
        // For inline scripts, write to temp file
        const extension = this.getScriptExtension(config.scriptType);
        const scriptFileName = `${config.id}${extension}`;
        const scriptPath = path.join(CRON_SCRIPTS_DIR, scriptFileName);
        if (config.inlineScript) {
            fs.writeFileSync(scriptPath, config.inlineScript, 'utf8');
            // Make executable for shell scripts
            if (config.scriptType === 'shell') {
                try {
                    fs.chmodSync(scriptPath, '755');
                }
                catch (err) {
                    console.warn('Could not set execute permission:', err);
                }
            }
        }
        return scriptPath;
    }
    /**
     * Get file extension for script type
     */
    getScriptExtension(scriptType) {
        switch (scriptType) {
            case 'node':
                return '.js';
            case 'python':
                return '.py';
            case 'shell':
                return '.sh';
            case 'dotnet':
                return '.cs';
            default:
                return '.txt';
        }
    }
    /**
     * Convert CronJobConfig to PM2 start options
     */
    toPM2Options(config) {
        const scriptPath = this.getScriptPath(config);
        const options = {
            name: `cron-${config.id}`,
            script: scriptPath,
            cron_restart: config.cronExpression,
            autorestart: false, // Don't auto-restart on crash, only on cron schedule
            watch: false,
            log_date_format: 'YYYY-MM-DD HH:mm:ss',
            combine_logs: true
        };
        // Set interpreter based on script type
        switch (config.scriptType) {
            case 'node':
                options.interpreter = 'node';
                break;
            case 'python':
                options.interpreter = 'python';
                break;
            case 'dotnet':
                options.interpreter = 'none';
                options.script = 'dotnet';
                options.args = ['run', '--project', config.scriptPath];
                break;
            case 'shell':
                options.interpreter = 'bash';
                options.interpreter_args = '-c';
                break;
        }
        if (config.args && config.args.length > 0) {
            options.args = config.args;
        }
        if (config.env) {
            options.env = config.env;
        }
        if (config.cwd) {
            options.cwd = config.cwd;
        }
        return options;
    }
    /**
     * Create a new cron job
     */
    async createCronJob(config) {
        const validation = this.validateCronExpression(config.cronExpression);
        if (!validation.valid) {
            throw new Error(`Invalid cron expression: ${validation.error}`);
        }
        const newConfig = {
            ...config,
            id: (0, uuid_1.v4)(),
            createdAt: new Date().toISOString(),
            updatedAt: new Date().toISOString()
        };
        const configs = this.readConfig();
        configs.push(newConfig);
        this.writeConfig(configs);
        // Start the PM2 process if enabled
        if (newConfig.enabled) {
            await this.startCronJob(newConfig.id);
        }
        return newConfig;
    }
    /**
     * Get all cron jobs
     */
    getCronJobs() {
        return this.readConfig();
    }
    /**
     * Get a single cron job by ID
     */
    getCronJob(id) {
        const configs = this.readConfig();
        return configs.find(c => c.id === id);
    }
    /**
     * Update a cron job
     */
    async updateCronJob(id, updates) {
        if (updates.cronExpression) {
            const validation = this.validateCronExpression(updates.cronExpression);
            if (!validation.valid) {
                throw new Error(`Invalid cron expression: ${validation.error}`);
            }
        }
        const configs = this.readConfig();
        const index = configs.findIndex(c => c.id === id);
        if (index === -1) {
            throw new Error('Cron job not found');
        }
        const wasEnabled = configs[index].enabled;
        configs[index] = {
            ...configs[index],
            ...updates,
            id, // Ensure ID doesn't change
            updatedAt: new Date().toISOString()
        };
        this.writeConfig(configs);
        // Handle PM2 process state changes
        if (wasEnabled && !configs[index].enabled) {
            await this.stopCronJob(id);
        }
        else if (!wasEnabled && configs[index].enabled) {
            await this.startCronJob(id);
        }
        else if (configs[index].enabled) {
            // Restart if enabled and config changed
            await this.stopCronJob(id);
            await this.startCronJob(id);
        }
        return configs[index];
    }
    /**
     * Delete a cron job
     */
    async deleteCronJob(id) {
        const configs = this.readConfig();
        const job = configs.find(c => c.id === id);
        if (!job) {
            throw new Error('Cron job not found');
        }
        // Stop PM2 process if running
        if (job.enabled) {
            await this.stopCronJob(id);
        }
        // Delete inline script file if it exists
        if (job.scriptMode === 'inline') {
            const extension = this.getScriptExtension(job.scriptType);
            const scriptPath = path.join(CRON_SCRIPTS_DIR, `${id}${extension}`);
            if (fs.existsSync(scriptPath)) {
                fs.unlinkSync(scriptPath);
            }
        }
        const filtered = configs.filter(c => c.id !== id);
        this.writeConfig(filtered);
    }
    /**
     * Start a cron job (start PM2 process with cron_restart)
     */
    async startCronJob(id) {
        const config = this.getCronJob(id);
        if (!config) {
            throw new Error('Cron job not found');
        }
        const pm2Options = this.toPM2Options(config);
        const pm2 = require('pm2');
        await (0, pm2_connection_1.executePM2Command)((callback) => {
            pm2.start(pm2Options, callback);
        });
    }
    /**
     * Stop a cron job (delete PM2 process)
     */
    async stopCronJob(id) {
        const processName = `cron-${id}`;
        const pm2 = require('pm2');
        try {
            await (0, pm2_connection_1.executePM2Command)((callback) => {
                pm2.delete(processName, (err) => {
                    // PM2 delete returns undefined on success, pass empty object
                    if (err && !err.message.includes('not found')) {
                        callback(err);
                    }
                    else {
                        callback(null, {});
                    }
                });
            });
        }
        catch (error) {
            // Ignore not found errors
            if (!error.message.includes('not found')) {
                throw error;
            }
        }
    }
    /**
     * Get status of all cron jobs (including PM2 process info)
     */
    async getCronJobsStatus() {
        const configs = this.readConfig();
        const pm2 = require('pm2');
        const list = await (0, pm2_connection_1.executePM2Command)((callback) => {
            pm2.list(callback);
        });
        const statuses = configs.map(config => {
            var _a;
            const processName = `cron-${config.id}`;
            const pm2Process = list.find((p) => p.name === processName);
            let nextExecution;
            if (config.enabled) {
                try {
                    const interval = cron_parser_1.CronExpressionParser.parse(config.cronExpression);
                    nextExecution = interval.next().toDate().toISOString();
                }
                catch (e) {
                    // Ignore
                }
            }
            return {
                config,
                pm2Process,
                isRunning: ((_a = pm2Process === null || pm2Process === void 0 ? void 0 : pm2Process.pm2_env) === null || _a === void 0 ? void 0 : _a.status) === 'online',
                nextExecution
            };
        });
        return statuses;
    }
    /**
     * Toggle cron job enabled state
     */
    async toggleCronJob(id) {
        const config = this.getCronJob(id);
        if (!config) {
            throw new Error('Cron job not found');
        }
        return this.updateCronJob(id, { enabled: !config.enabled });
    }
}
exports.CronJobService = CronJobService;
exports.default = CronJobService.getInstance();
