import { serverLogger } from "../utils/serverLogger.js";
import { Scanner } from "./ScannerClass.js";
import { getDatabase } from "./database.js";
import * as cron from "node-cron";
import { EventEmitter } from "events";
export class ScheduledScannerService extends EventEmitter {
    appState;
    database;
    schedules = new Map();
    tasks = new Map();
    currentProgress = new Map();
    scanner;
    db;
    isInitialized = false;
    // Maintenance windows implemented via cron expressions in scheduledScannerRoutes.ts
    // - Add database optimization schedule (VACUUM, ANALYZE)
    // - Implement backup scheduling with retention policies
    // - Add update checks with auto-install option
    // - Create maintenance mode that pauses streaming
    // - Add resource usage monitoring during maintenance
    constructor(appState, database){
        super(), this.appState = appState, this.database = database;
        this.db = database;
        this.scanner = new Scanner(appState, database);
        this.setupDefaultSchedules();
    }
    setupDefaultSchedules() {
        // Default schedules that users can modify
        const defaults = [
            {
                id: "daily-full",
                name: "Daily Full Scan",
                enabled: false,
                cronExpression: "0 3 * * *",
                scanType: "full",
                status: "idle"
            },
            {
                id: "hourly-quick",
                name: "Hourly Quick Scan",
                enabled: false,
                cronExpression: "0 * * * *",
                scanType: "quick",
                status: "idle"
            },
            {
                id: "weekly-deep",
                name: "Weekly Deep Scan",
                enabled: false,
                cronExpression: "0 2 * * 0",
                scanType: "full",
                status: "idle"
            }
        ];
        defaults.forEach((schedule)=>{
            this.schedules.set(schedule.id, schedule);
        });
    }
    async initialize() {
        if (this.isInitialized) return;
        try {
            // Load saved schedules from database
            await this.loadSchedules();
            // Start active schedules
            for (const schedule of this.schedules.values()){
                if (schedule.enabled) {
                    this.startSchedule(schedule.id);
                }
            }
            this.isInitialized = true;
            serverLogger.info("Scheduled scanner service initialized", {
                activeSchedules: Array.from(this.schedules.values()).filter((s)=>s.enabled).length
            });
        } catch (error) {
            serverLogger.error("Failed to initialize scheduled scanner", error);
            throw error;
        }
    }
    async loadSchedules() {
        try {
            const stmt = this.db.prepare(`
        SELECT * FROM scan_schedules
      `);
            const rows = stmt.all();
            rows.forEach((row)=>{
                const schedule = {
                    id: row.id,
                    name: row.name,
                    enabled: row.enabled === 1,
                    cronExpression: row.cron_expression,
                    libraryPaths: row.library_paths ? JSON.parse(row.library_paths) : undefined,
                    scanType: row.scan_type,
                    lastRun: row.last_run ? new Date(row.last_run) : undefined,
                    nextRun: row.next_run ? new Date(row.next_run) : undefined,
                    status: row.status || "idle",
                    lastDuration: row.last_duration,
                    lastItemsScanned: row.last_items_scanned
                };
                this.schedules.set(schedule.id, schedule);
            });
        } catch (error) {
            // If database access fails, just use defaults
            serverLogger.warn("Could not load schedules from database, using defaults");
        // this.loadDefaultSchedules(); // Method not implemented
        }
    }
    async saveSchedule(schedule) {
        try {
            const stmt = this.db.prepare(`
      INSERT OR REPLACE INTO scan_schedules (
        id, name, enabled, cron_expression, library_paths,
        scan_type, last_run, next_run, status,
        last_duration, last_items_scanned
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `);
            stmt.run(schedule.id, schedule.name, schedule.enabled ? 1 : 0, schedule.cronExpression, schedule.libraryPaths ? JSON.stringify(schedule.libraryPaths) : null, schedule.scanType, schedule.lastRun?.toISOString(), schedule.nextRun?.toISOString(), schedule.status, schedule.lastDuration, schedule.lastItemsScanned);
        } catch (error) {
            serverLogger.warn("Could not save schedule to database", error);
        }
    }
    async createSchedule(schedule) {
        const id = `custom-${Date.now()}`;
        const newSchedule = {
            ...schedule,
            id,
            status: "idle"
        };
        this.schedules.set(id, newSchedule);
        await this.saveSchedule(newSchedule);
        if (newSchedule.enabled) {
            this.startSchedule(id);
        }
        serverLogger.info("Created scan schedule", {
            id,
            name: schedule.name
        });
        this.emit("scheduleCreated", newSchedule);
        return newSchedule;
    }
    async updateSchedule(id, updates) {
        const schedule = this.schedules.get(id);
        if (!schedule) {
            throw new Error(`Schedule ${id} not found`);
        }
        const wasEnabled = schedule.enabled;
        const updatedSchedule = {
            ...schedule,
            ...updates
        };
        this.schedules.set(id, updatedSchedule);
        await this.saveSchedule(updatedSchedule);
        // Handle enable/disable changes
        if (wasEnabled && !updatedSchedule.enabled) {
            this.stopSchedule(id);
        } else if (!wasEnabled && updatedSchedule.enabled) {
            this.startSchedule(id);
        } else if (updatedSchedule.enabled && updates.cronExpression) {
            // Restart if cron expression changed
            this.stopSchedule(id);
            this.startSchedule(id);
        }
        serverLogger.info("Updated scan schedule", {
            id,
            updates
        });
        this.emit("scheduleUpdated", updatedSchedule);
        return updatedSchedule;
    }
    async deleteSchedule(id) {
        const schedule = this.schedules.get(id);
        if (!schedule) {
            throw new Error(`Schedule ${id} not found`);
        }
        this.stopSchedule(id);
        this.schedules.delete(id);
        const stmt = this.db.prepare("DELETE FROM scan_schedules WHERE id = ?");
        stmt.run(id);
        serverLogger.info("Deleted scan schedule", {
            id
        });
        this.emit("scheduleDeleted", id);
    }
    startSchedule(id) {
        const schedule = this.schedules.get(id);
        if (!schedule) return;
        // Stop existing task if any
        this.stopSchedule(id);
        try {
            const task = cron.schedule(schedule.cronExpression, async ()=>{
                await this.executeScan(id);
            });
            this.tasks.set(id, task);
            // Calculate next run time
            const nextRun = this.calculateNextRun(schedule.cronExpression);
            schedule.nextRun = nextRun;
            this.saveSchedule(schedule);
            serverLogger.info("Started schedule", {
                id,
                name: schedule.name,
                nextRun
            });
        } catch (error) {
            serverLogger.error("Failed to start schedule", {
                id,
                error
            });
            schedule.status = "failed";
            this.saveSchedule(schedule);
        }
    }
    stopSchedule(id) {
        const task = this.tasks.get(id);
        if (task) {
            task.stop();
            this.tasks.delete(id);
            serverLogger.info("Stopped schedule", {
                id
            });
        }
    }
    calculateNextRun(cronExpression) {
        // Parse cron expression to calculate next run time
        const cronTime = cron.validate(cronExpression);
        if (!cronTime) {
            return new Date(Date.now() + 24 * 60 * 60 * 1000); // Default to 24 hours
        }
        // This is a simplified calculation
        // In production, use a proper cron parser library
        const parts = cronExpression.split(" ");
        const now = new Date();
        const next = new Date(now);
        // Simple daily schedule detection
        if (parts[0] !== "*" && parts[1] !== "*") {
            next.setHours(parseInt(parts[1]), parseInt(parts[0]), 0, 0);
            if (next <= now) {
                next.setDate(next.getDate() + 1);
            }
        }
        return next;
    }
    async executeScan(scheduleId) {
        const schedule = this.schedules.get(scheduleId);
        if (!schedule) return;
        // Use atomic database update to prevent race conditions
        const updateStmt = this.db.prepare("UPDATE scan_schedules SET status = 'running' WHERE id = ? AND status != 'running'");
        const updateResult = updateStmt.run(scheduleId);
        if (updateResult.changes === 0) {
            serverLogger.warn("Scan already running for schedule", {
                scheduleId
            });
            return;
        }
        const startTime = new Date();
        const progress = {
            scheduleId,
            status: "preparing",
            totalItems: 0,
            processedItems: 0,
            discoveredItems: 0,
            errors: 0,
            startTime,
            progressPercentage: 0
        };
        try {
            // Update local state to match database
            schedule.status = "running";
            this.currentProgress.set(scheduleId, progress);
            this.emit("scanStarted", progress);
            serverLogger.info("Starting scheduled scan", {
                scheduleId,
                name: schedule.name,
                type: schedule.scanType
            });
            // Get library paths
            const libraryPaths = schedule.libraryPaths || this.appState.getAllLibraries();
            if (libraryPaths.length === 0) {
                throw new Error("No library paths configured");
            }
            progress.status = "scanning";
            progress.totalItems = await this.estimateItemCount(libraryPaths);
            this.emit("scanProgress", progress);
            // Track scan progress
            let processedCount = 0;
            const progressCallback = (current, total, path)=>{
                processedCount = current;
                progress.processedItems = current;
                progress.totalItems = total;
                progress.currentPath = path;
                progress.progressPercentage = total > 0 ? current / total * 100 : 0;
                // Estimate time remaining based on current speed
                const elapsed = (Date.now() - startTime.getTime()) / 1000;
                const speed = processedCount / elapsed;
                const remaining = total - current;
                progress.estimatedTimeRemaining = speed > 0 ? remaining / speed : undefined;
                this.emit("scanProgress", progress);
            };
            // Execute scan based on type
            let scanResult;
            switch(schedule.scanType){
                case "quick":
                    scanResult = await this.scanner.quickScan(libraryPaths, progressCallback);
                    break;
                case "partial":
                    scanResult = await this.scanner.partialScan(libraryPaths, progressCallback);
                    break;
                case "full":
                default:
                    scanResult = await this.scanner.fullScan(libraryPaths, progressCallback);
                    break;
            }
            const duration = (Date.now() - startTime.getTime()) / 1000;
            // Update schedule with results
            schedule.status = "idle";
            schedule.lastRun = startTime;
            schedule.lastDuration = duration;
            schedule.lastItemsScanned = scanResult.totalScanned;
            schedule.nextRun = this.calculateNextRun(schedule.cronExpression);
            await this.saveSchedule(schedule);
            // Update progress
            progress.status = "completed";
            progress.processedItems = scanResult.totalScanned;
            progress.discoveredItems = scanResult.newItems;
            progress.errors = scanResult.errors;
            progress.progressPercentage = 100;
            this.emit("scanCompleted", {
                scheduleId,
                duration,
                result: scanResult
            });
            // Invalidate cached shuffle indexes so they reflect new scan results
            try {
                getDatabase().invalidateShuffleCache();
            } catch  {
            // Non-critical: cache will self-expire via TTL
            }
            serverLogger.info("Scheduled scan completed", {
                scheduleId,
                name: schedule.name,
                duration,
                scanned: scanResult.totalScanned,
                new: scanResult.newItems,
                errors: scanResult.errors
            });
        } catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            const errorStack = error instanceof Error ? error.stack : undefined;
            serverLogger.error("Scheduled scan failed", {
                scheduleId,
                error: errorMessage,
                stack: errorStack
            });
            schedule.status = "failed";
            await this.saveSchedule(schedule);
            progress.status = "failed";
            progress.errors++;
            progress.errorDetails = errorMessage;
            this.emit("scanFailed", {
                scheduleId,
                error: errorMessage,
                details: errorStack
            });
        } finally{
            // Clean up progress after a delay
            setTimeout(()=>{
                this.currentProgress.delete(scheduleId);
            }, 60000); // Keep for 1 minute
        }
    }
    async estimateItemCount(paths) {
        // Quick estimation based on directory traversal
        // This is a simplified version - in production, use more sophisticated estimation
        return 1000; // Placeholder
    }
    async triggerManualScan(scheduleId) {
        const schedule = this.schedules.get(scheduleId);
        if (!schedule) {
            throw new Error(`Schedule ${scheduleId} not found`);
        }
        await this.executeScan(scheduleId);
    }
    getSchedules() {
        return Array.from(this.schedules.values());
    }
    getSchedule(id) {
        return this.schedules.get(id);
    }
    getProgress(scheduleId) {
        return this.currentProgress.get(scheduleId);
    }
    getAllProgress() {
        return Array.from(this.currentProgress.values());
    }
    // Utility method to validate cron expression
    validateCronExpression(expression) {
        return cron.validate(expression);
    }
    // Get suggested cron expressions
    getCronSuggestions() {
        return [
            {
                label: "Every hour",
                value: "0 * * * *",
                description: "Runs at the start of every hour"
            },
            {
                label: "Every 30 minutes",
                value: "*/30 * * * *",
                description: "Runs every 30 minutes"
            },
            {
                label: "Daily at 3 AM",
                value: "0 3 * * *",
                description: "Runs once daily at 3 AM"
            },
            {
                label: "Daily at midnight",
                value: "0 0 * * *",
                description: "Runs once daily at midnight"
            },
            {
                label: "Weekly on Sunday",
                value: "0 2 * * 0",
                description: "Runs weekly on Sunday at 2 AM"
            },
            {
                label: "Monthly",
                value: "0 0 1 * *",
                description: "Runs on the 1st of each month"
            },
            {
                label: "Every 6 hours",
                value: "0 */6 * * *",
                description: "Runs every 6 hours"
            },
            {
                label: "Weekdays at 9 AM",
                value: "0 9 * * 1-5",
                description: "Runs Mon-Fri at 9 AM"
            }
        ];
    }
    async shutdown() {
        // Stop all scheduled tasks
        this.tasks.forEach((task, id)=>{
            task.stop();
            serverLogger.info("Stopped schedule on shutdown", {
                id
            });
        });
        this.tasks.clear();
        this.currentProgress.clear();
        this.removeAllListeners();
    }
}

//# sourceMappingURL=scheduledScanner.js.map