// src/routes/api/save-crop-video/+server.ts
import type { RequestHandler } from './$types';
import { error } from '@sveltejs/kit';
import { spawn, exec } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs/promises';
import * as path from 'path';
import pool from '$lib/server/database'; // 1. DB 풀
import { activeProcesses } from '$lib/server/processes'; // 2. 프로세스 매니저

const execAsync = promisify(exec);

// --- 경로 설정 ---
// (주의) 이 경로는 SvelteKit 서버가 실행되는 '호스트' 기준입니다. (컨테이너 내부가 아님)
// '/home/ssdoctors/project/BigDataPolyp/tmp/process.mp4' (이전 로그 기준)
const TEMP_FILE_PATH = path.resolve(process.cwd(), 'tmp', 'process.mp4'); 
const ORIGINAL_DIR = '/workspace/image/video/original';
const CROP_DIR = '/workspace/image/video/crop';

// --- 헬퍼 함수 ---

/**
 * SSE(Server-Sent Events) 메시지 포맷으로 데이터를 전송합니다.
 */
function sendEvent(controller: ReadableStreamDefaultController, data: object) {
	controller.enqueue(`data: ${JSON.stringify(data)}\n\n`);
}

/**
 * FFmpeg의 시간 문자열(예: 00:01:30.50)을 초 단위로 변환합니다.
 */
function parseFFmpegTime(timeString: string): number {
	const [hours, minutes, seconds] = timeString.split(':').map(parseFloat);
	return hours * 3600 + minutes * 60 + seconds;
}

/**
 * ffprobe를 실행하여 동영상의 총 길이를 초 단위로 반환합니다.
 */
async function getDuration(filePath: string): Promise<number> {
	try {
		const command = `ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${filePath}"`;
		const { stdout } = await execAsync(command);
		return parseFloat(stdout);
	} catch (e) {
		console.error('ffprobe duration error:', e);
		throw new Error('동영상 길이를 가져오는 데 실패했습니다.');
	}
}

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)}`;
};

/**
 * TumerMovie 테이블에 비디오 정보를 등록합니다.
 */

async function registerInDB(filePath: string, movieType: 1 | 2, originalPath: string) {
    const client = await pool.connect();
    try {
        // 1. [수정] 더 빠른 ffprobe 명령어 (duration과 fps 사용)
        const ffprobeCommand = `ffprobe -v error -select_streams v:0 -show_entries stream=duration,avg_frame_rate -of default=noprint_wrappers=1:nokey=1 "${filePath}"`;
        
        // stdout 예시:
        // 30000/1001  (avg_frame_rate)
        // 120.500000  (duration)
        const { stdout } = await execAsync(ffprobeCommand);
        const [fpsString, durationString] = stdout.trim().split('\n');

        // '30000/1001' 같은 분수 파싱
        const fpsParts = fpsString.split('/');
        const avg_fps = parseFloat(fpsParts[0]) / (parseFloat(fpsParts[1] || '1'));
        const duration = parseFloat(durationString);

		const durationStartStr = formatDurationForDB(0);
        const durationEndStr = formatDurationForDB(duration);

        // 총 프레임 수 추정
        const totalFrameCount = Math.round(duration * avg_fps);

        if (isNaN(totalFrameCount)) {
            throw new Error('TotalFrameCount 계산 실패');
        }

        const query = `
            INSERT INTO "TumerMovie" ("LocationFile", "MovieType", "created_at", "original_path", "TotalFrameCount", "durationStart", "durationEnd")
            VALUES ($1, $2, NOW(), $3, $4, $5, $6)
            RETURNING "Index";
        `;
        const result = await client.query(query, [filePath, movieType, originalPath, totalFrameCount, durationStartStr, durationEndStr]);
        console.log(`[DB] MovieType ${movieType} 저장 완료 (Index: ${result.rows[0].Index}) FrameCount: ${totalFrameCount} (추정)`);
    } catch (e) {
        console.error(`[DB] MovieType ${movieType} 저장 실패:`, e);
        throw new Error(`DB 저장 실패 (Type: ${movieType})`);
    } finally {
        client.release();
    }
}

/*
async function registerInDB(filePath: string, movieType: 1 | 2, originalPath: string) {
	const client = await pool.connect();
	try {

		// 1. FFprobe로 TotalFrameCount 계산 및 TumerMovie에 INSERT
        const ffprobeCommand = `ffprobe -v error -select_streams v:0 -count_frames -show_entries stream=nb_read_frames -of default=nokey=1:noprint_wrappers=1 "${filePath}"`;
        const { stdout } = await execAsync(ffprobeCommand);
        const totalFrameCount = parseInt(stdout.trim(), 10);

		const query = `
            INSERT INTO "TumerMovie" ("LocationFile", "MovieType", "created_at", "original_path", "TotalFrameCount")
            VALUES ($1, $2, NOW(), $3, $4)
            RETURNING "Index";
        `;
		const result = await client.query(query, [filePath, movieType, originalPath, totalFrameCount]);
		console.log(`[DB] MovieType ${movieType} 저장 완료 (Index: ${result.rows[0].Index}) FrameCount: ${totalFrameCount}`);
	} catch (e) {
		console.error(`[DB] MovieType ${movieType} 저장 실패:`, e);
		throw new Error(`DB 저장 실패 (Type: ${movieType})`);
	} finally {
		client.release();
	}
}
	*/

// --- API 핸들러 ---

export const GET: RequestHandler = ({ url }) => {
	const cropParams = url.searchParams.get('crop');
	const originalFileName = url.searchParams.get('fileName');

	if (!cropParams || !originalFileName) {
		throw error(400, 'crop 파라미터와 fileName이 필요합니다.');
	}

	let ffmpegProcess: ReturnType<typeof spawn> | null = null;
	const processId = `crop-${Date.now()}`;

	const stream = new ReadableStream({
		async start(controller) {
			sendEvent(controller, { processId });

			try {
				// 0. 대상 디렉토리 생성
				await fs.mkdir(ORIGINAL_DIR, { recursive: true });
				await fs.mkdir(CROP_DIR, { recursive: true });

				// 1. 파일명 및 경로 정의
				const baseName = path.parse(originalFileName).name;
				const ext = path.parse(originalFileName).ext;
				//const newOriginalFileName = `${baseName}_${processId}${ext}`;

				// 현재 시각을 YYYYMMDD_HHmmss 형식으로 포맷
				const now = new Date();
				const yyyy = now.getFullYear();
				const MM = String(now.getMonth() + 1).padStart(2, '0');
				const dd = String(now.getDate()).padStart(2, '0');
				const HH = String(now.getHours()).padStart(2, '0');
				const mm = String(now.getMinutes()).padStart(2, '0');
				const ss = String(now.getSeconds()).padStart(2, '0');
				const timestamp = `${yyyy}${MM}${dd}_${HH}${mm}${ss}`;

				// crop 파일 이름 변경
				const newCropFileName = `${baseName}_crop-${timestamp}${ext}`;

				const originalDestPath = path.join(ORIGINAL_DIR, originalFileName);
				const cropDestPath = path.join(CROP_DIR, newCropFileName);

				// 2. 동영상 총 길이 가져오기 (프로그레스 계산용)
				const totalDuration = await getDuration(TEMP_FILE_PATH);
				sendEvent(controller, { message: `동영상 총 길이: ${totalDuration.toFixed(2)}초` });

				// 3. [작업 1] 원본 파일 복사 및 DB 등록 (MovieType: 2)
				sendEvent(controller, { message: '원본 파일 저장 중...' });
				await fs.copyFile(TEMP_FILE_PATH, originalDestPath);
				await registerInDB(originalDestPath, 2, originalDestPath);
				sendEvent(controller, { message: '원본 파일 저장 완료.' });


				// 4. [작업 2] Crop 파일 생성 (MovieType: 1)
				sendEvent(controller, { message: 'Crop 동영상 생성 시작...' });
				/*
				const args = [
					'-i', originalDestPath,
					'-vf', `crop=${cropParams}`,
					'-c:a', 'copy', // 오디오는 재인코딩 없이 복사
					'-y', // 덮어쓰기
					cropDestPath
				];
				*/

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

				ffmpegProcess = spawn('ffmpeg', args);
				activeProcesses.set(processId, ffmpegProcess);

				// FFmpeg 진행률 파싱
				ffmpegProcess.stderr.on('data', (data: Buffer) => {
					const line = data.toString();
					// 예: time=00:00:10.54
					const timeMatch = line.match(/time=(\d{2}:\d{2}:\d{2}\.\d{2})/); 
					if (timeMatch) {
						const currentTime = parseFFmpegTime(timeMatch[1]);
						let progress = (currentTime / totalDuration) * 100;
						progress = Math.min(Math.max(progress, 0), 100); // 0~100% 범위 보장
						sendEvent(controller, { progress: progress });
					}
				});

				// FFmpeg 종료 이벤트
				ffmpegProcess.on('close', async (code) => {
					activeProcesses.delete(processId);
					if (code === 0) {
						// 성공
						sendEvent(controller, { progress: 100 });
						await registerInDB(cropDestPath, 1, originalDestPath);
						sendEvent(controller, { status: 'completed' });
					} else {
						// 0이 아닌 코드는 오류 또는 취소
						console.error(`FFmpeg exited with code ${code}`);
						if (code === 255) { // SIGTERM에 의한 종료 코드 (OS마다 다를 수 있음)
							sendEvent(controller, { error: '작업이 사용자에 의해 취소되었습니다.' });
						} else {
							sendEvent(controller, { error: `FFmpeg 처리 실패 (Code: ${code})` });
						}
					}
					// 5. [작업 3] 임시 파일 삭제
					await fs.unlink(TEMP_FILE_PATH).catch(e => console.error("임시 파일 삭제 실패:", e));
					controller.close();
				});

				// FFmpeg 스폰 오류
				ffmpegProcess.on('error', (err) => {
					activeProcesses.delete(processId);
					console.error('FFmpeg spawn error:', err);
					sendEvent(controller, { error: `FFmpeg 실행 오류: ${err.message}` });
					controller.close();
				});

			} catch (err) {
				const errorMessage = (err as Error).message;
				console.error('save-crop-video API error:', errorMessage);
				sendEvent(controller, { error: errorMessage });
				controller.close();
				// 오류 발생 시에도 임시 파일 삭제 시도
				await fs.unlink(TEMP_FILE_PATH).catch(e => console.error("오류 후 임시 파일 삭제 실패:", e));
			}
		},
		cancel() {
			// 클라이언트가 EventSource.close()를 호출하거나 연결이 끊겼을 때
			if (ffmpegProcess) {
				console.log(`[${processId}] 클라이언트 연결 끊김. 프로세스 종료 중...`);
				ffmpegProcess.kill('SIGTERM');
				activeProcesses.delete(processId);
			}
		}
	});

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