import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import Database from 'better-sqlite3';
import { runMigrations } from '../../db/migrations.js';
import { SchedulerService } from '../scheduler-service.js';
import { JobRunner } from '../job-runner.js';
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import YAML from 'yaml';
// Mock node-cron
vi.mock('node-cron', () => ({
    default: {
        schedule: vi.fn(() => ({ stop: vi.fn(), start: vi.fn() })),
        validate: vi.fn(() => true),
    },
}));
// Mock JobRunner
vi.mock('../job-runner.js', () => ({
    JobRunner: vi.fn().mockImplementation(() => ({
        run: vi.fn().mockResolvedValue('completed'),
        isRunning: vi.fn().mockReturnValue(false),
        stopAll: vi.fn(),
    })),
}));
describe('SchedulerService', () => {
    let db;
    let service;
    let squadsDir;
    beforeEach(() => {
        db = new Database(':memory:');
        runMigrations(db);
        squadsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'scheduler-test-'));
        const mockRunner = new JobRunner({});
        service = new SchedulerService({ db, jobRunner: mockRunner, squadsDir });
    });
    afterEach(() => {
        service.stop();
        db.close();
        fs.rmSync(squadsDir, { recursive: true, force: true });
    });
    function createSquadWithSchedule(name, schedule) {
        const dir = path.join(squadsDir, name);
        fs.mkdirSync(dir, { recursive: true });
        fs.writeFileSync(path.join(dir, 'squad.yaml'), YAML.stringify({
            squad: {
                code: name,
                name: `Squad ${name}`,
                description: 'Test squad',
                icon: '🤖',
                agents: [{ id: 'a1', name: 'Agent 1', icon: '🤖', prompt: 'test' }],
                pipeline: { steps: [{ id: 's1', agent: 'a1', label: 'Step 1' }] },
                schedule,
            },
        }));
    }
    it('should register cron jobs on start', () => {
        createSquadWithSchedule('squad-a', {
            enabled: true,
            cron: '0 9 * * 1',
            mode: 'autonomous',
            prompt: 'Generate report',
        });
        service.start();
        const status = service.getStatus();
        expect(status).toHaveLength(1);
        expect(status[0].squadName).toBe('squad-a');
        expect(status[0].status).toBe('active');
    });
    it('should skip squads without schedule.enabled', () => {
        createSquadWithSchedule('squad-no-schedule', {
            enabled: false,
            cron: '0 9 * * 1',
            mode: 'autonomous',
            prompt: 'Skip me',
        });
        service.start();
        expect(service.getStatus()).toHaveLength(0);
    });
    it('should pause and resume a squad', () => {
        createSquadWithSchedule('squad-b', {
            enabled: true,
            cron: '0 9 * * 1',
            mode: 'autonomous',
            prompt: 'Test',
        });
        service.start();
        service.pause('squad-b');
        const paused = service.getStatus();
        expect(paused[0].status).toBe('paused');
        // Check DB state
        const row = db.prepare('SELECT status FROM scheduled_jobs WHERE squad_name = ?').get('squad-b');
        expect(row.status).toBe('paused');
        service.resume('squad-b');
        const resumed = service.getStatus();
        expect(resumed[0].status).toBe('active');
    });
    it('should unregister a squad', () => {
        createSquadWithSchedule('squad-c', {
            enabled: true,
            cron: '0 9 * * 1',
            mode: 'autonomous',
            prompt: 'Test',
        });
        service.start();
        expect(service.getStatus()).toHaveLength(1);
        service.unregister('squad-c');
        expect(service.getStatus()).toHaveLength(0);
    });
    it('should register a job dynamically from API data', () => {
        const ok = service.registerDynamic('api-squad', {
            cron: '0 8 * * *',
            prompt: 'Daily report',
            timezone: 'America/Sao_Paulo',
            timeout_minutes: 15,
            retry_on_failure: 2,
        });
        expect(ok).toBe(true);
        const status = service.getStatus();
        expect(status).toHaveLength(1);
        expect(status[0].squadName).toBe('api-squad');
        expect(status[0].cronExpression).toBe('0 8 * * *');
        expect(status[0].status).toBe('active');
        const row = db.prepare('SELECT * FROM scheduled_jobs WHERE squad_name = ?').get('api-squad');
        expect(row.prompt).toBe('Daily report');
        expect(row.timezone).toBe('America/Sao_Paulo');
        expect(row.timeout_minutes).toBe(15);
        expect(row.retry_on_failure).toBe(2);
    });
    it('should update existing job via registerDynamic', () => {
        service.registerDynamic('update-squad', {
            cron: '0 8 * * *',
            prompt: 'Old prompt',
        });
        service.registerDynamic('update-squad', {
            cron: '0 10 * * *',
            prompt: 'New prompt',
        });
        const status = service.getStatus();
        expect(status).toHaveLength(1);
        expect(status[0].cronExpression).toBe('0 10 * * *');
    });
    it('should restore API-created jobs from DB on start', () => {
        // Simulate a previous server session that created a job via API
        db.prepare(`
      INSERT INTO scheduled_jobs (squad_name, cron_expression, prompt, mode, timeout_minutes, retry_on_failure, timezone, status)
      VALUES (?, ?, ?, 'autonomous', ?, ?, ?, 'active')
    `).run('api-squad', '0 8 * * *', 'Daily report', 15, 2, 'America/Sao_Paulo');
        // Start a fresh service — no YAML exists for api-squad
        service.start();
        const status = service.getStatus();
        expect(status).toHaveLength(1);
        expect(status[0].squadName).toBe('api-squad');
        expect(status[0].cronExpression).toBe('0 8 * * *');
        expect(status[0].status).toBe('active');
    });
    it('should restore paused jobs from DB on start', () => {
        db.prepare(`
      INSERT INTO scheduled_jobs (squad_name, cron_expression, prompt, mode, timeout_minutes, retry_on_failure, timezone, status)
      VALUES (?, ?, ?, 'autonomous', ?, ?, ?, 'paused')
    `).run('paused-squad', '0 9 * * 1', 'Weekly check', 30, 0, null);
        service.start();
        const status = service.getStatus();
        expect(status).toHaveLength(1);
        expect(status[0].squadName).toBe('paused-squad');
        expect(status[0].status).toBe('paused');
    });
    it('should not duplicate jobs that exist in both YAML and DB', () => {
        // Create YAML squad
        createSquadWithSchedule('squad-both', {
            enabled: true,
            cron: '0 9 * * 1',
            mode: 'autonomous',
            prompt: 'From YAML',
        });
        // Also insert into DB (as if it was saved from a previous start)
        db.prepare(`
      INSERT INTO scheduled_jobs (squad_name, cron_expression, prompt, mode, timeout_minutes, retry_on_failure, status)
      VALUES (?, ?, ?, 'autonomous', 30, 0, 'active')
    `).run('squad-both', '0 9 * * 1', 'From DB');
        service.start();
        // Should only have 1 entry, not 2
        const status = service.getStatus();
        expect(status).toHaveLength(1);
        expect(status[0].squadName).toBe('squad-both');
    });
    it('should reload a specific squad config', () => {
        createSquadWithSchedule('squad-d', {
            enabled: true,
            cron: '0 9 * * 1',
            mode: 'autonomous',
            prompt: 'Old prompt',
        });
        service.start();
        expect(service.getStatus()).toHaveLength(1);
        // Update the YAML
        createSquadWithSchedule('squad-d', {
            enabled: true,
            cron: '0 10 * * 1',
            mode: 'autonomous',
            prompt: 'New prompt',
        });
        service.reload('squad-d');
        const status = service.getStatus();
        expect(status[0].cronExpression).toBe('0 10 * * 1');
    });
});
//# sourceMappingURL=scheduler-service.test.js.map