import { error } from '@sveltejs/kit';
import { spawn, exec } from 'child_process';
import * as fs from 'fs/promises';
import * as path from 'path';
import { promisify } from 'util';
import pool from '../../../lib/server/database'; // DB 풀 가져오기

const execAsync = promisify(exec);

// [삭제] 고정된 FINAL_OUTPUT_DIR 및 관련 폴더 생성 로직을 제거하고 동적으로 생성합니다.

interface TimeSegment {
    id: number;
    start: number;
    end: number;
}

/**
 * FFmpeg '자르기' 진행률 파서 (stdout)
 * out_time=... 또는 time=... 에서 시간을 초 단위로 변환합니다.
 */
const parseFfmpegPipeProgress = (data: string): number | null => {
    const lines = data.split('\n');
    for (const line of lines) {
        const key = line.startsWith('out_time=') ? 'out_time=' : (line.startsWith('time=') ? 'time=' : null);
        if (key) {
            const timeStr = line.substring(key.length).trim();
            const timeMatch = timeStr.match(/(\d{2}):(\d{2}):(\d{2})\.(\d+)/);
            if(timeMatch) {
                const hours = parseInt(timeMatch[1], 10);
                const minutes = parseInt(timeMatch[2], 10);
                const seconds = parseInt(timeMatch[3], 10);
                const fraction = parseFloat(`0.${timeMatch[4]}`);
                return hours * 3600 + minutes * 60 + seconds + fraction;
            }
        }
    }
    return null;
};

/**
 * FFmpeg '프레임 추출' 진행률 파서 (stderr)
 * 'frame= 123' 형식에서 프레임 수를 추출합니다.
 */
const parseFfmpegFrameProgress = (data: string): number | null => {
    const match = data.match(/frame=\s*(\d+)/);
    if (match && match[1]) {
        return parseInt(match[1], 10);
    }
    return null;
};

/**
 * 현재 시간을 'YYYYMMDD_HHmmSS' 형식으로 반환합니다.
 */
const getCurrentTimestamp = (): string => {
    const now = new Date();
    const pad = (num: number) => num.toString().padStart(2, '0');
    
    const year = now.getFullYear();
    const month = pad(now.getMonth() + 1);
    const day = pad(now.getDate());
    
    const hours = pad(now.getHours());
    const minutes = pad(now.getMinutes());
    const seconds = pad(now.getSeconds());
    
    return `${year}${month}${day}_${hours}${minutes}${seconds}`;
};

/**
 * [추가] 오늘 날짜를 'YYYYMMDD' 형식으로 반환하는 헬퍼
 */
const getCurrentDateFolder = (): string => {
    const now = new Date();
    const pad = (num: number) => num.toString().padStart(2, '0');
    const year = now.getFullYear();
    const month = pad(now.getMonth() + 1);
    const day = pad(now.getDate());
    return `${year}${month}${day}`;
};

/**
 * 초 단위 시간을 'HHmmSS_sss' 형식의 문자열로 변환합니다.
 */
const formatDurationForFilename = (timeInSeconds: number): string => {
    if (isNaN(timeInSeconds) || timeInSeconds < 0) return '000000_000';
    
    const pad = (num: number, size: number) => num.toString().padStart(size, '0');
    
    const hours = Math.floor(timeInSeconds / 3600);
    const minutes = Math.floor((timeInSeconds % 3600) / 60);
    const seconds = Math.floor(timeInSeconds % 60);
    const milliseconds = Math.round((timeInSeconds - Math.floor(timeInSeconds)) * 1000);
    
    return `${pad(hours, 2)}${pad(minutes, 2)}${pad(seconds, 2)}_${pad(milliseconds, 3)}`;
};

/**
 * 초 단위 시간을 DB 저장용 'HH:mm:SS.sss' 형식으로 변환합니다.
 */
const formatDurationForDB = (timeInSeconds: number): string => {
    if (isNaN(timeInSeconds) || timeInSeconds < 0) return '00:00:00.000';
    
    const pad = (num: number, size: number) => num.toString().padStart(size, '0');
    
    const hours = Math.floor(timeInSeconds / 3600);
    const minutes = Math.floor((timeInSeconds % 3600) / 60);
    const seconds = Math.floor(timeInSeconds % 60);
    const milliseconds = Math.round((timeInSeconds - Math.floor(timeInSeconds)) * 1000);
    
    return `${pad(hours, 2)}:${pad(minutes, 2)}:${pad(seconds, 2)}.${pad(milliseconds, 3)}`;
};

/**
 * 잘라낸 비디오를 DB에 등록하고 프레임을 추출하는 함수
 */
const registerVideoInDB = async (
    movieFilePath: string, 
    cutMovieFileName: string,
    enqueue: (data: object) => void,
    segmentId: number,
    durationStart: number,
    durationEnd: number,
    originalPath: string // [추가] 원본 동영상 경로
): Promise<number> => {
    const client = await pool.connect();
    let tumerMovieIndex: number;
    let totalFrameCount: number;

    try {
        await client.query('BEGIN');

        // 1. FFprobe로 전체 프레임 수 빠르게 추정
        enqueue({ type: 'progress_stage_start', stage: 'frames', segmentId: segmentId, message: '전체 프레임 계산 중...' });
        
        const ffprobeCommand = `ffprobe -v error -select_streams v:0 -show_entries stream=duration,avg_frame_rate -of default=noprint_wrappers=1:nokey=1 "${movieFilePath}"`;
        const { stdout } = await execAsync(ffprobeCommand);
        
        const [fpsString, durationString] = stdout.trim().split('\n');
        if (!fpsString || !durationString) throw new Error(`FFprobe failed to get duration/fps for ${movieFilePath}. Output: ${stdout}`);
        const fpsParts = fpsString.split('/');
        const avg_fps = parseFloat(fpsParts[0]) / (parseFloat(fpsParts[1] || '1'));
        const duration = parseFloat(durationString);
        totalFrameCount = Math.round(duration * avg_fps);
        if (isNaN(totalFrameCount) || totalFrameCount <= 0) totalFrameCount = 1;
        
        enqueue({ type: 'progress_stage_start', stage: 'frames', segmentId: segmentId, totalFrames: totalFrameCount });

        // [수정] 2. TumerMovie에 INSERT (original_path 추가)
        const locationNetwork = 'localhost'; 
        const objectType = 0;
        const durationStartStr = formatDurationForDB(durationStart);
        const durationEndStr = formatDurationForDB(durationEnd);
        const movieInsertQuery = `
            INSERT INTO public."TumerMovie" (
                "LocationNetwork", "LocationFile", "TotalFrameCount", "ObjectType", "durationStart", "durationEnd", "original_path"
            ) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "Index"`;
        const movieInsertRes = await client.query(movieInsertQuery, [
            locationNetwork, movieFilePath, totalFrameCount, objectType, durationStartStr, durationEndStr, originalPath
        ]);
        tumerMovieIndex = movieInsertRes.rows[0].Index;
        console.log(`DB: TumerMovie 저장 완료 (Index: ${tumerMovieIndex})`);

        // 3. FFmpeg로 모든 프레임 추출
        const videoFileNameWithoutExt = cutMovieFileName.split('.').slice(0, -1).join('.');
        const imageRefBaseDir = path.join('/workspace/image/image_ref', videoFileNameWithoutExt);
        const tempFrameDir = path.join(imageRefBaseDir, 'temp_frames');
        await fs.mkdir(tempFrameDir, { recursive: true });

        await new Promise<void>((resolve, reject) => {
            //const ffmpeg = spawn('ffmpeg', ['-i', movieFilePath, `${tempFrameDir}/%d.png`]);
            const ffmpeg = spawn('ffmpeg', [
                '-i', movieFilePath,
                '-vf', 'mpdecimate,setpts=N/FRAME_RATE/TB',
                '-r', '60',
                `${tempFrameDir}/%d.png`
                ]);
            let stderr = '';
            ffmpeg.stderr.on('data', (data: Buffer) => {
                const dataStr = data.toString();
                stderr += dataStr;
                const currentFrame = parseFfmpegFrameProgress(dataStr);
                if (currentFrame !== null) {
                    const progress = Math.min(100, (currentFrame / totalFrameCount) * 100);
                    enqueue({ type: 'progress', stage: 'frames', segmentId: segmentId, progress: progress });
                }
            });
            ffmpeg.on('close', (code) => code === 0 ? resolve() : reject(new Error(`FFmpeg (frame extraction) exited with code ${code}. Stderr: ${stderr}`)));
            ffmpeg.on('error', reject);
        });

        // 4. 프레임 재구성 및 DB 저장
        enqueue({ type: 'progress_stage_start', stage: 'db_save', segmentId: segmentId });

        const extractedFrames = await fs.readdir(tempFrameDir);
        
        // [수정] 프레임 파일 목록을 숫자(frameNumber) 기준으로 정렬하고, 숫자가 아닌 파일은 제외합니다.
        const sortedFrames = extractedFrames
            .map(fileName => {
                const frameNumber = parseInt(path.basename(fileName, '.png'), 10);
                return { fileName, frameNumber };
            })
            .filter(frame => !isNaN(frame.frameNumber)) // 숫자로 변환할 수 없는 파일(e.g., .DS_Store) 제외
            .sort((a, b) => a.frameNumber - b.frameNumber); // 숫자 오름차순으로 정렬

        const datasetRefValues: (string | number)[] = [];
        const queryParams: string[] = [];
        let paramIndex = 1;

        // [수정] 정렬된 프레임 목록으로 반복
        for (const frame of sortedFrames) {
            const { frameNumber, fileName } = frame;
            const shardDirNumber = Math.floor((frameNumber - 1) / 1000);
            const finalDir = path.join(imageRefBaseDir, String(shardDirNumber));
            await fs.mkdir(finalDir, { recursive: true });

            const oldPath = path.join(tempFrameDir, fileName);
            const newPath = path.join(finalDir, `${frameNumber}.png`);
            await fs.rename(oldPath, newPath);

            const dbLocationFile = newPath;
            queryParams.push(`($${paramIndex++}, $${paramIndex++}, $${paramIndex++})`);
            datasetRefValues.push(tumerMovieIndex, frameNumber, dbLocationFile);
        }
        await fs.rm(tempFrameDir, { recursive: true, force: true });
        
        if (datasetRefValues.length > 0) {
            const datasetRefQuery = `INSERT INTO public."TumerDatasetRef" ("IndexMovie", "DataNumber", "LocationFile") VALUES ${queryParams.join(', ')}`;
            await client.query(datasetRefQuery, datasetRefValues);
        }

        await client.query('COMMIT');
        return tumerMovieIndex;

    } catch (err) {
        await client.query('ROLLBACK');
        console.error(`[${cutMovieFileName}] DB 처리 중 오류 발생:`, err);
        throw err;
    } finally {
        client.release();
    }
};

export const GET: import('@sveltejs/kit').RequestHandler = ({ url }) => {
    const segmentsParam = url.searchParams.get('segments');
    const videoIndexParam = url.searchParams.get('videoIndex');

    if (!segmentsParam || !videoIndexParam || isNaN(Number(videoIndexParam))) {
         throw error(400, '유효한 segments와 videoIndex가 필요합니다.');
    }

    const segments: TimeSegment[] = JSON.parse(segmentsParam);
    let isStreamClosed = false;

    const stream = new ReadableStream({
        async start(controller) {
            const encoder = new TextEncoder();
            const enqueue = (data: object) => {
                if (isStreamClosed) return;
                try {
                    controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
                } catch (e: any) {
                    if (e.code === 'ERR_INVALID_STATE' || e.name === 'TypeError') {
                        isStreamClosed = true;
                    } else { throw e; }
                }
            };

            let sourceVideoPath: string;

            try {
                const client = await pool.connect();
                try {
                    const query = `SELECT "LocationFile" FROM "TumerMovie" WHERE "Index" = $1 AND "MovieType" = 1;`;
                    const result = await client.query(query, [videoIndexParam]);
                    if (result.rows.length === 0) throw new Error(`DB에서 Index ${videoIndexParam}에 해당하는 동영상을 찾을 수 없습니다.`);
                    sourceVideoPath = result.rows[0].LocationFile;
                    await fs.access(sourceVideoPath, fs.constants.F_OK);
                } finally {
                    client.release();
                }

                const processSegment = (segment: TimeSegment): Promise<void> => {
                    return new Promise(async (resolve, reject) => {
                        try {
                            const segmentDuration = segment.end - segment.start;
                            if (segmentDuration <= 0) return reject(new Error(`Segment ${segment.id} has invalid duration.`));
                            
                            const todaysDate = getCurrentDateFolder();
                            const sourceFileNameBase = path.basename(sourceVideoPath, path.extname(sourceVideoPath));
                            const timestamp = getCurrentTimestamp();
                            const durationStart = formatDurationForFilename(segment.start);
                            const durationEnd = formatDurationForFilename(segment.end);
                            const outputFileName = `${sourceFileNameBase}-cut-${timestamp}_${durationStart}_${durationEnd}.mp4`;
                            const newOutputDir = path.join('/workspace/image/video/bigdata', todaysDate);
                            
                            await fs.mkdir(newOutputDir, { recursive: true });
                            
                            const outputPath = path.join(newOutputDir, outputFileName);

                            const ffmpegArgs = [
                                '-ss', segment.start.toString(), '-i', sourceVideoPath, 
                                '-t', segmentDuration.toString(), '-c:v', 'h264_nvenc', 
                                '-qp', '5', '-r', '60', '-c:a', 'copy', '-progress', 'pipe:1', '-y', outputPath
                            ];
                            
                            const ffmpeg = spawn('ffmpeg', ffmpegArgs);

                            ffmpeg.stdout.on('data', (data: Buffer) => {
                                const time = parseFfmpegPipeProgress(data.toString());
                                if (time !== null) {
                                    const progress = Math.min(100, (time / segmentDuration) * 100);
                                    enqueue({ type: 'progress', stage: 'cut', segmentId: segment.id, progress });
                                }
                            });
                            
                            let stderr = '';
                            ffmpeg.stderr.on('data', (data: Buffer) => { stderr += data.toString(); });

                            ffmpeg.on('close', async (code) => {
                                if (code === 0) {
                                    try {
                                        console.log(`[${outputFileName}] 1단계: FFmpeg cut complete. 2단계: DB/Frame 시작...`);
                                        
                                        // [수정] registerVideoInDB 호출 시 sourceVideoPath 전달
                                        const newIndex = await registerVideoInDB(
                                            outputPath, 
                                            outputFileName, 
                                            enqueue, 
                                            segment.id,
                                            segment.start,
                                            segment.end,
                                            sourceVideoPath // <-- 원본 경로 전달
                                        );
                                        
                                        const resultUrl = `/cuts/${outputFileName}`;
                                        
                                        enqueue({ type: 'done', segmentId: segment.id, url: resultUrl, newIndex: newIndex });
                                        resolve();
                                    } catch (dbError) {
                                        reject(dbError);
                                    }
                                } else {
                                    reject(new Error(`FFmpeg (cut) exited with code ${code}. Stderr: ${stderr}`));
                                }
                            });

                            ffmpeg.on('error', reject);
                        } catch (err) {
                            reject(err);
                        }
                    });
                };

                await Promise.all(segments.map(segment => processSegment(segment)));
                enqueue({ type: 'all_complete' });

            } catch (err: any) {
                console.error('Segment cutting or DB processing failed (main catch):', err);
                enqueue({ type: 'error', error: err.message || '알 수 없는 오류가 발생했습니다.' });
            } finally {
                if (!isStreamClosed) {
                    isStreamClosed = true;
                    controller.close();
                }
            }
        },
        cancel(reason) {
            console.warn(`[Stream] Stream canceled by client. Reason: ${reason}`);
            isStreamClosed = true;
        }
    });

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

