import { getConfig as loadConfig } from '../../shared/config';
import { queryOne } from '../../shared/database';
import crypto from 'crypto';
import fs from 'fs';
import path from 'path';
import { spawn } from 'child_process';

const { values } = loadConfig();
const FFMPEG_PATH = values.paths?.ffmpeg || 'ffmpeg';
const FFPROBE_PATH = values.paths?.ffprobe || 'ffprobe';

const STREAM_TTL = 2 * 60 * 60 * 1000;

type VideoStreamInfo = {
    index: number;
    codec: string;
    width: number;
    height: number;
    fps: number;
};

type AudioStreamInfo = {
    index: number;
    codec: string;
    language?: string;
    title?: string;
    channels: number;
    sampleRate: number;
};

type SubtitleStreamInfo = {
    index: number;
    codec: string;
    language?: string;
    title?: string;
    duration: number;
};

type ChapterInfo = {
    id: number;
    start: number;
    end: number;
    title: string;
};

type MediaInfo = {
    video: VideoStreamInfo[];
    audio: AudioStreamInfo[];
    subtitles: SubtitleStreamInfo[];
    chapters: ChapterInfo[];
    duration: number;
};

type ActiveStream = {
    hash: string;
    filePath: string;
    hlsDir: string;
    info: MediaInfo;
    process?: any;
    startedAt: number;
    lastAccessed: number;
    isComplete: boolean;
};

const activeStreams = new Map<string, ActiveStream>();

setInterval(() => {
    const now = Date.now();

    for (const [hash, stream] of activeStreams.entries()) {
        const age = now - stream.lastAccessed;

        if (age > STREAM_TTL) {
            console.log(`🗑️  Limpiando stream antiguo: ${hash}`);

            if (stream.process && !stream.process.killed) {
                stream.process.kill('SIGKILL');
            }

            if (fs.existsSync(stream.hlsDir)) {
                fs.rmSync(stream.hlsDir, { recursive: true, force: true });
            }

            activeStreams.delete(hash);
        }
    }
}, 60 * 1000);

export function getStreamHash(filePath: string): string {
    const stat = fs.statSync(filePath);
    return crypto
        .createHash('md5')
        .update(`${filePath}-${stat.mtime.getTime()}`)
        .digest('hex');
}

function ensureHLSDirectory(hash: string): string {
    const tempDir = path.join(require('os').tmpdir(), 'hls-streams', hash);

    if (!fs.existsSync(tempDir)) {
        fs.mkdirSync(tempDir, { recursive: true });
    }

    return tempDir;
}

function buildStreamMap(info: MediaInfo): string {
    const maps: string[] = [];

    maps.push(`v:0,a:0`);

    return maps.join(' ');
}

function writeMasterPlaylist(info: MediaInfo, hlsDir: string) {
    const lines: string[] = ['#EXTM3U'];

    info.audio.forEach((a, i) => {
        lines.push(
            `#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="${a.title || `Audio ${i+1}`}",LANGUAGE="${a.language || 'und'}",AUTOSELECT=YES,DEFAULT=${i===0?'YES':'NO'},URI="audio_${i}.m3u8"`
        );
    });

    info.subtitles.forEach((s, i) => {
        lines.push(
            `#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="${s.title || `Sub ${i+1}`}",LANGUAGE="${s.language || 'und'}",AUTOSELECT=YES,DEFAULT=NO,URI="subs_${i}.m3u8"`
        );
    });

    const v = info.video[0];
    const bandwidth = v.width * v.height * v.fps * 0.07 | 0;

    lines.push(
        `#EXT-X-STREAM-INF:BANDWIDTH=${bandwidth},RESOLUTION=${v.width}x${v.height},AUDIO="audio"${info.subtitles.length ? ',SUBTITLES="subs"' : ''}`
    );
    lines.push('video.m3u8');

    fs.writeFileSync(path.join(hlsDir, 'master.m3u8'), lines.join('\n'));
}

function startHLSConversion(filePath: string, info: MediaInfo, hash: string): ActiveStream {
    const hlsDir = ensureHLSDirectory(hash);

    const stream: ActiveStream = {
        hash,
        filePath,
        hlsDir,
        info,
        process: null,
        startedAt: Date.now(),
        lastAccessed: Date.now(),
        isComplete: false
    };

    activeStreams.set(hash, stream);

    (async () => {
        try {

            await extractSubtitles(filePath, info, hlsDir);

            writeMasterPlaylist(info, hlsDir);

            startVideoTranscoding(stream, filePath, info, hlsDir);

        } catch (err) {
            console.error('❌ Error en el flujo de inicio:', err);
        }
    })();

    return stream;
}

function startVideoTranscoding(stream: ActiveStream, filePath: string, info: MediaInfo, hlsDir: string) {
    const args: string[] = ['-i', filePath];

    args.push(
        '-map', '0:v:0',
        '-c:v', 'libx264',
        '-preset', 'veryfast',
        '-profile:v', 'main',
        '-g', '48',
        '-keyint_min', '48',
        '-sc_threshold', '0',
        '-pix_fmt', 'yuv420p',
        '-f', 'hls',
        '-hls_time', '4',
        '-hls_playlist_type', 'event',

        '-hls_flags', 'independent_segments',
        '-hls_segment_filename', path.join(hlsDir, 'v_%05d.ts'),
        path.join(hlsDir, 'video.m3u8')
    );

    info.audio.forEach((a, i) => {
        args.push(
            '-map', `0:${a.index}`,
            `-c:a:${i}`, 'aac',
            `-b:a:${i}`, '128k',
            '-f', 'hls',
            '-hls_time', '4',
            '-hls_playlist_type', 'event',
            '-hls_flags', 'independent_segments',
            '-hls_segment_filename', path.join(hlsDir, `a${i}_%05d.ts`),
            path.join(hlsDir, `audio_${i}.m3u8`)
        );
    });

    console.log('🎬 Starting Video/Audio transcoding:', args.join(' '));

    const ffmpeg = spawn(FFMPEG_PATH, args, {
        windowsHide: true,
        stdio: ['ignore', 'pipe', 'pipe']
    });

    stream.process = ffmpeg;

    ffmpeg.stderr.on('data', (data) => {
        const text = data.toString();

        if (text.includes('time=')) {
            const timeMatch = text.match(/time=(\S+)/);
            if (timeMatch) console.log(`⏱️  Converting ${stream.hash.substr(0,6)}: ${timeMatch[1]}`);
        }
    });

    ffmpeg.on('close', (code) => {
        if (code === 0) {
            console.log('✅ Video transcoding complete');
            stream.isComplete = true;

        }
    });
}

const SUBTITLE_EXTENSIONS: Record<string, string> = {
    'ass': 'ass',
    'ssa': 'ass',
    'subrip': 'srt',
    'webvtt': 'vtt',
    'hdmv_pgs_subtitle': 'sup',
    'mov_text': 'srt',
    'dvd_subtitle': 'sub',
    'text': 'srt'
};

async function extractSubtitles(filePath: string, info: MediaInfo, hlsDir: string): Promise<void> {
    if (info.subtitles.length === 0) return;

    console.log('📝 Extrayendo subtítulos...');

    const promises = info.subtitles.map((s, i) => {
        return new Promise<void>((resolve, reject) => {
            const codec = s.codec.toLowerCase();
            const ext = codec === 'subrip' ? 'srt'
                : codec === 'ass' || codec === 'ssa' ? 'ass'
                    : codec === 'webvtt' ? 'vtt'
                        : 'sub';

            const outputFilename = `s${i}_full.${ext}`;
            const outputPath = path.join(hlsDir, outputFilename);
            const playlistPath = path.join(hlsDir, `subs_${i}.m3u8`);

            if (s.duration === 0) {
                console.log(`⚠️ Sub vacío, skip: ${i}`);
                createDummySubtitlePlaylist(playlistPath, outputFilename, info.duration);
                fs.writeFileSync(outputPath, '');
                resolve();
                return;
            }

            console.log(`  Track ${i}: ${s.title || s.language || 'Unknown'} [${codec}] -> ${ext}`);

            const args = [
                '-i', filePath,
                '-map', `0:s:${i}`
            ];

            args.push('-c:s', 'copy');
            args.push('-y', outputPath);

            console.log(`  Comando: ffmpeg ${args.join(' ')}`);

            const p = spawn(FFMPEG_PATH, args, {
                stdio: ['ignore', 'pipe', 'pipe']
            });

            let errorOutput = '';

            p.stderr.on('data', (data) => {
                errorOutput += data.toString();
            });

            p.on('close', (code) => {
                if (code === 0) {
                    try {

                        if (!fs.existsSync(outputPath)) {
                            console.error(`  ❌ Archivo no creado: ${outputFilename}`);

                            fs.writeFileSync(outputPath, '');
                        } else {
                            const stat = fs.statSync(outputPath);
                            if (stat.size === 0) {
                                console.error(`  ⚠️ Subtítulo ${i} tiene 0 bytes`);
                                console.error(`  FFmpeg stderr:`, errorOutput.slice(-500));
                            } else {
                                console.log(`  ✅ Subtítulo ${i} extraído: ${stat.size} bytes`);
                            }
                        }

                        createDummySubtitlePlaylist(playlistPath, outputFilename, info.duration);
                        resolve();
                    } catch (e) {
                        console.error(`  Error procesando subtítulo ${i}:`, e);
                        resolve();
                    }
                } else {
                    console.error(`  ❌ Error extrayendo subtítulo ${i}. Exit code: ${code}`);
                    console.error(`  FFmpeg stderr:`, errorOutput.slice(-500));

                    fs.writeFileSync(outputPath, '');
                    createDummySubtitlePlaylist(playlistPath, outputFilename, info.duration);
                    resolve();
                }
            });

            p.on('error', (err) => {
                console.error(`  Error spawn ffmpeg subs ${i}:`, err);
                fs.writeFileSync(outputPath, '');
                createDummySubtitlePlaylist(playlistPath, outputFilename, info.duration);
                resolve();
            });
        });
    });

    await Promise.all(promises);
    console.log('✅ Proceso de subtítulos finalizado.');
}

function createDummySubtitlePlaylist(playlistPath: string, subtitleFilename: string, duration: number) {
    const content = [
        '#EXTM3U',
        '#EXT-X-VERSION:3',
        `#EXT-X-TARGETDURATION:${Math.ceil(duration)}`,
        '#EXT-X-MEDIA-SEQUENCE:0',
        '#EXT-X-PLAYLIST-TYPE:VOD',
        `#EXTINF:${duration.toFixed(6)},`,
        subtitleFilename,
        '#EXT-X-ENDLIST'
    ].join('\n');

    fs.writeFileSync(playlistPath, content);
}

async function probeMediaFile(filePath: string): Promise<MediaInfo> {
    return new Promise((resolve, reject) => {
        const args = [
            '-v', 'quiet',
            '-print_format', 'json',
            '-show_streams',
            '-show_chapters',
            '-show_format',
            filePath
        ];

        const ffprobe = spawn(FFPROBE_PATH, args);
        let output = '';

        ffprobe.stdout.on('data', (data) => {
            output += data.toString();
        });

        ffprobe.on('close', (code) => {
            if (code !== 0) {
                return reject(new Error(`FFprobe failed with code ${code}`));
            }

            try {
                const data = JSON.parse(output);

                const video: VideoStreamInfo[] = data.streams
                    .filter((s: any) => s.codec_type === 'video')
                    .map((s: any) => ({
                        index: s.index,
                        codec: s.codec_name,
                        width: s.width,
                        height: s.height,
                        fps: eval(s.r_frame_rate) || 24
                    }));

                const audio: AudioStreamInfo[] = data.streams
                    .filter((s: any) => s.codec_type === 'audio')
                    .map((s: any) => ({
                        index: s.index,
                        codec: s.codec_name,
                        language: s.tags?.language,
                        title: s.tags?.title,
                        channels: s.channels || 2,
                        sampleRate: parseInt(s.sample_rate) || 48000
                    }));

                const subtitles: SubtitleStreamInfo[] = data.streams
                    .filter((s: any) => s.codec_type === 'subtitle')
                    .map((s: any) => ({
                        index: s.index,
                        codec: s.codec_name,
                        language: s.tags?.language,
                        title: s.tags?.title,
                        duration: parseFloat(s.duration) || 0
                    }));

                const chapters: ChapterInfo[] = (data.chapters || []).map((c: any) => ({
                    id: c.id,
                    start: parseFloat(c.start_time),
                    end: parseFloat(c.end_time),
                    title: c.tags?.title || `Chapter ${c.id + 1}`
                }));

                const duration = parseFloat(data.format?.duration) || 0;

                console.log(`📊 Media info: ${video.length} video, ${audio.length} audio, ${subtitles.length} subs`);

                resolve({ video, audio, subtitles, chapters, duration });
            } catch (e) {
                reject(e);
            }
        });

        ffprobe.on('error', reject);
    });
}

export async function getStreamingManifest(filePath: string) {
    if (!fs.existsSync(filePath)) {
        return null;
    }

    const hash = getStreamHash(filePath);

    const formatSubtitles = (subs: SubtitleStreamInfo[]) => {
        return subs.map((s, i) => {
            const ext = SUBTITLE_EXTENSIONS[s.codec] || 'vtt';
            return {
                index: s.index,
                codec: s.codec,
                language: s.language || 'und',
                title: s.title || `Subtitle ${s.index}`,

                url: `/api/library/hls/${hash}/s${i}_full.${ext}`
            };
        });
    };

    const existing = activeStreams.get(hash);
    if (existing) {
        existing.lastAccessed = Date.now();

        return {
            type: 'hls',
            hash,
            masterPlaylist: `/api/library/hls/${hash}/master.m3u8`,
            duration: existing.info.duration,
            isComplete: existing.isComplete,
            video: existing.info.video.map(v => ({
                index: v.index,
                codec: v.codec,
                resolution: `${v.width}x${v.height}`,
                fps: v.fps
            })),
            audio: existing.info.audio.map(a => ({
                index: a.index,
                codec: a.codec,
                language: a.language || 'und',
                title: a.title || `Audio ${a.index}`,
                channels: a.channels
            })),

            subtitles: formatSubtitles(existing.info.subtitles),
            chapters: existing.info.chapters.map(c => ({
                id: c.id,
                start: c.start,
                end: c.end,
                title: c.title
            }))
        };
    }

    const duplicateCheck = activeStreams.get(hash);

    if (duplicateCheck) {
        duplicateCheck.lastAccessed = Date.now();
        return {
            type: 'hls',
            hash,
            masterPlaylist: `/api/library/hls/${hash}/master.m3u8`,
            duration: duplicateCheck.info.duration,
            isComplete: duplicateCheck.isComplete,
            video: duplicateCheck.info.video.map(v => ({
                index: v.index,
                codec: v.codec,
                resolution: `${v.width}x${v.height}`,
                fps: v.fps
            })),
            audio: duplicateCheck.info.audio.map(a => ({
                index: a.index,
                codec: a.codec,
                language: a.language || 'und',
                title: a.title || `Audio ${a.index}`,
                channels: a.channels
            })),
            subtitles: formatSubtitles(duplicateCheck.info.subtitles),
            chapters: duplicateCheck.info.chapters.map(c => ({
                id: c.id,
                start: c.start,
                end: c.end,
                title: c.title
            }))
        };
    }

    const info = await probeMediaFile(filePath);
    return {
        type: 'hls',
        hash,
        masterPlaylist: `/api/library/hls/${hash}/master.m3u8`,
        duration: info.duration,
        isComplete: false,
        generating: true,
        video: info.video.map(v => ({
            index: v.index,
            codec: v.codec,
            resolution: `${v.width}x${v.height}`,
            fps: v.fps
        })),
        audio: info.audio.map(a => ({
            index: a.index,
            codec: a.codec,
            language: a.language || 'und',
            title: a.title || `Audio ${a.index}`,
            channels: a.channels
        })),
        subtitles: formatSubtitles(info.subtitles),
        chapters: info.chapters.map(c => ({
            id: c.id,
            start: c.start,
            end: c.end,
            title: c.title
        }))
    };
}

export function getSubtitleFileStream(hash: string, trackIndex: number) {
    const stream = activeStreams.get(hash);

    if (!stream) {

        const tempDir = path.join(require('os').tmpdir(), 'hls-streams', hash);
        if(fs.existsSync(tempDir)) {

            const files = fs.readdirSync(tempDir);
            const subFile = files.find(f => f.startsWith(`s${trackIndex}_full.`));
            if (subFile) return fs.createReadStream(path.join(tempDir, subFile));
        }
        return null;
    }

    if (!fs.existsSync(stream.hlsDir)) return null;

    const files = fs.readdirSync(stream.hlsDir);
    const subtitleFile = files.find(f => f.startsWith(`s${trackIndex}_full.`));

    if (!subtitleFile) return null;

    return fs.createReadStream(path.join(stream.hlsDir, subtitleFile));
}

export async function getHLSFile(hash: string, filename: string) {
    const stream = activeStreams.get(hash);

    if (!stream) {
        return null;
    }

    stream.lastAccessed = Date.now();

    const filePath = path.join(stream.hlsDir, filename);

    const maxWait = 30000;

    const startWait = Date.now();

    while (!fs.existsSync(filePath)) {
        if (Date.now() - startWait > maxWait) {
            console.error(`⏱️  Timeout esperando archivo: ${filename}`);
            return null;
        }

        if (stream.isComplete && !fs.existsSync(filePath)) {
            return null;
        }

        await new Promise(resolve => setTimeout(resolve, 100));
    }

    return {
        path: filePath,
        stat: fs.statSync(filePath)
    };
}

export function getActiveStreamsStatus() {
    return Array.from(activeStreams.values()).map(stream => ({
        hash: stream.hash,
        filePath: stream.filePath,
        isComplete: stream.isComplete,
        startedAt: stream.startedAt,
        lastAccessed: stream.lastAccessed,
        age: Date.now() - stream.startedAt,
        idle: Date.now() - stream.lastAccessed
    }));
}