// src/routes/video/crop-progress/+server.ts
import { type RequestHandler } from '@sveltejs/kit';
import { spawn, exec } from 'child_process';
import * as fs from 'fs';
import * as util from 'util';
import * as path from 'path';
import { activeProcesses, processInfo } from '$lib/server/processes';

const execPromise = util.promisify(exec);

async function getVideoDuration(filePath: string): Promise<number> {
    try {
        const { stdout } = await execPromise(
            `ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${filePath}"`
        );
        return parseFloat(stdout);
    } catch {
        return 0;
    }
}

// ✅ Node 종료 시 FFmpeg 종료
process.on('exit', () => {
    for (const [id, proc] of activeProcesses.entries()) {
        try {
            proc.kill('SIGKILL');
        } catch {}
    }
});

// ✅ 좀비 감시
setInterval(() => {
    exec('ps -eo pid,ppid,stat,comm | grep ffmpeg | grep Z', (err, stdout) => {
        if (stdout.trim()) console.warn('⚠️ Zombie ffmpeg:\n', stdout);
    });
}, 60000);

// ✅ GET: SSE Crop Progress
export const GET: RequestHandler = ({ request }) => {
    const url = new URL(request.url);
    const cropParams = url.searchParams.get('crop');

    if (!cropParams || !/^\d+:\d+:\d+:\d+$/.test(cropParams)) {
        return new Response('Invalid crop parameters', { status: 400 });
    }

    const inputStream = new ReadableStream({
        async start(controller) {
            const processId = `crop-${Date.now()}`;
            const inputPath = path.join(process.cwd(), 'tmp', 'process.mp4');
            const outputPath = path.join(process.cwd(), 'tmp', `crop_${processId}.mp4`);

            const enqueue = (data: string) => {
                try { controller.enqueue(data); } catch {}
            };
            const close = () => {
                try { controller.close(); } catch {}
            };

            const totalDuration = await getVideoDuration(inputPath);
            if (totalDuration === 0) {
                enqueue('data: {"error": "Failed to get duration"}\n\n');
                close();
                return;
            }

            const args = [
                '-i', inputPath,
                '-vf', `crop=${cropParams}`,
                '-qp', '10',
                '-c:v', 'h264_nvenc',
                '-c:a', 'copy',
                '-y', outputPath,
                '-progress', 'pipe:1'
            ];

            const proc = spawn('ffmpeg', args);
            activeProcesses.set(processId, proc);
            processInfo.set(processId, {
                id: processId,
                startTime: Date.now(),
                status: 'running',
                progress: 0
            });

            enqueue(`data: ${JSON.stringify({ processId })}\n\n`);

            proc.stdout.on('data', (d) => {
                const str = d.toString();
                const match = str.match(/out_time_ms=(\d+)/);
                if (match) {
                    const t = parseInt(match[1], 10) / 1_000_000;
                    const progress = Math.round((t / totalDuration) * 100);
                    processInfo.get(processId)!.progress = progress;
                    enqueue(`data: ${JSON.stringify({ progress })}\n\n`);
                }
            });

            proc.on('close', async (code, signal) => {
                activeProcesses.delete(processId);
                const info = processInfo.get(processId);
                if (code === 0) {
                    info!.status = 'completed';
                    info!.progress = 100;
                    try {
                        await fs.promises.rename(outputPath, inputPath);
                        enqueue(`data: ${JSON.stringify({ progress: 100, status: 'completed' })}\n\n`);
                    } catch {
                        enqueue(`data: ${JSON.stringify({ error: 'rename failed' })}\n\n`);
                    }
                } else {
                    info!.status = proc.killed ? 'cancelled' : 'error';
                    enqueue(`data: ${JSON.stringify({ error: 'failed' })}\n\n`);
                    await fs.promises.rm(outputPath, { force: true });
                }
                close();
            });

            proc.on('error', (e) => {
                enqueue(`data: ${JSON.stringify({ error: e.message })}\n\n`);
                processInfo.get(processId)!.status = 'error';
                close();
            });
        }
    });

    return new Response(inputStream, {
        headers: {
            'Content-Type': 'text/event-stream',
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive'
        }
    });
};
