import {FastifyReply, FastifyRequest} from 'fastify';
import * as animeService from './anime.service';
import { setActivity } from '../rpc/rp.service';
import { upsertListEntry } from '../list/list.service';
import {getExtension} from '../../shared/extensions';
import {Anime, AnimeRequest, SearchRequest, WatchStreamRequest} from '../types';
import {spawn} from "node:child_process";
import net from 'net';
import fs from 'fs';
import os from 'os';
import path from 'path';
import jwt from "jsonwebtoken";

export async function getAnime(req: AnimeRequest, reply: FastifyReply) {
    try {
        const { id } = req.params;
        const source = req.query.source;

        let anime: Anime | { error: string };
        if (source === 'anilist') {
            anime = await animeService.getAnimeById(id);
        } else {
            const ext = getExtension(source);
            anime = await animeService.getAnimeInfoExtension(ext, id)
        }

        return anime;
    } catch (err) {
        return { error: "Database error" };
    }
}

export async function getAnimeEpisodes(req: AnimeRequest, reply: FastifyReply) {
    try {
        const { id } = req.params;
        const source = req.query.source || 'anilist';
        const ext = getExtension(source);

        return await animeService.searchEpisodesInExtension(
            ext,
            source,
            id
        );
    } catch (err) {
        return { error: "Database error" };
    }
}

export async function getTrending(req: FastifyRequest, reply: FastifyReply) {
    try {
        const results = await animeService.getTrendingAnime();
        return { results };
    } catch (err) {
        return { results: [] };
    }
}

export async function getTopAiring(req: FastifyRequest, reply: FastifyReply) {
    try {
        const results = await animeService.getTopAiringAnime();
        return { results };
    } catch (err) {
        return { results: [] };
    }
}

export async function search(req: SearchRequest, reply: FastifyReply) {
    try {
        const query = req.query.q;
        const results = await animeService.searchAnimeLocal(query);

        if (results.length > 0) {
            return { results: results };
        }

    } catch (err) {
        return { results: [] };
    }
}

export async function searchInExtension(req: any, reply: FastifyReply) {
    try {
        const extensionName = req.params.extension;
        const query = req.query.q;

        const ext = getExtension(extensionName);
        if (!ext) return { results: [] };

        const results = await animeService.searchAnimeInExtension(ext, extensionName, query);
        return { results };
    } catch {
        return { results: [] };
    }
}

export async function getWatchStream(req: WatchStreamRequest, reply: FastifyReply) {
    try {
        const { animeId, episode, server, category, ext, source } = req.query;

        const extension = getExtension(ext);
        if (!extension) return { error: "Extension not found" };

        return await animeService.getStreamData(
            extension,
            episode,
            animeId,
            source,
            server,
            category
        );
    } catch (err) {
        const error = err as Error;
        return { error: error.message };
    }
}

export async function openInMPV(req: any, reply: any) {
    try {
        const { title, video, subtitles = [], chapters = [], animeId, episode, entrySource, token } = req.body;

        if (!video?.url) return { error: 'Missing video url' };

        const PORT = 54322;
        const proxyBase = `http://localhost:${PORT}/api/proxy`;
        const mediaTitle = title || 'Anime';

        const proxyVideo =
            `${proxyBase}?url=${encodeURIComponent(video.url)}` +
            `&referer=${encodeURIComponent(video.headers?.Referer ?? '')}` +
            `&origin=${encodeURIComponent(video.headers?.Origin ?? '')}` +
            `&userAgent=${encodeURIComponent(video.headers?.['User-Agent'] ?? '')}`;

        const proxySubs = subtitles.map((s: any) =>
            `${proxyBase}?url=${encodeURIComponent(s.src)}` +
            `&referer=${encodeURIComponent(video.headers?.Referer ?? '')}` +
            `&origin=${encodeURIComponent(video.headers?.Origin ?? '')}` +
            `&userAgent=${encodeURIComponent(video.headers?.['User-Agent'] ?? '')}`
        );

        const pipe = `\\\\.\\pipe\\mpv-${Date.now()}`;

        let chaptersArg: string[] = [];
        if (chapters.length) {

            chapters.sort((a: any, b: any) => a.startTime - b.startTime);

            const lines = [';FFMETADATA1'];

            for (let i = 0; i < chapters.length; i++) {
                const c = chapters[i];

                const start = Math.floor(c.startTime * 1000);
                const end = Math.floor(c.endTime * 1000);

                const title = (c.type || 'chapter').toUpperCase();

                lines.push(
                    `[CHAPTER]`, `TIMEBASE=1/1000`, `START=${start}`, `END=${end}`, `title=${title}`
                );

                if (i < chapters.length - 1) {
                    const nextStart = Math.floor(chapters[i + 1].startTime * 1000);

                    if (nextStart - end > 1000) {
                        lines.push(
                            `[CHAPTER]`, `TIMEBASE=1/1000`, `START=${end}`, `END=${nextStart}`, `title=Episode`
                        );
                    }
                } else {
                    lines.push(
                        `[CHAPTER]`, `TIMEBASE=1/1000`, `START=${end}`, `title=Episode`
                    );
                }
            }

            const chaptersFile = path.join(os.tmpdir(), `mpv-chapters-${Date.now()}.txt`);
            fs.writeFileSync(chaptersFile, lines.join('\n'));
            chaptersArg = [`--chapters-file=${chaptersFile}`];
        }

        spawn(
            'D:\\mpv\\mpv.exe',
            [
                '--force-window=yes',
                '--idle=yes',
                '--keep-open=yes',
                '--no-terminal',
                '--hwdec=auto',
                `--force-media-title=Loading...`,

                `--input-ipc-server=${pipe}`,
                ...chaptersArg
            ],
            { stdio: 'ignore', windowsHide: true, detached: true }
        ).unref();

        await new Promise(r => setTimeout(r, 400));
        const socket = net.connect(pipe);

        let duration = 0;
        let currentTime = 0;
        let isPaused = false;
        let titleChanged = false;
        let progressUpdated = false;

        const sendRPC = (paused: boolean) => {
            let startTimestamp = undefined;
            let endTimestamp = undefined;

            if (!paused && duration > 0) {
                const now = Math.floor(Date.now() / 1000);
                const elapsed = Math.floor(currentTime);

                startTimestamp = now - elapsed;
                endTimestamp = startTimestamp + Math.floor(duration);
            }
            setActivity({
                details: mediaTitle,
                state: `Episode ${episode}`,
                mode: "watching",
                paused: paused,
                startTimestamp: startTimestamp,
                endTimestamp: endTimestamp
            })
        };

        const updateProgress = async () => {
            if (!token || progressUpdated) return;
            try {
                const decoded = jwt.verify(token, process.env.JWT_SECRET!) as any;
                const userId = decoded.id;
                await upsertListEntry({
                    user_id: userId,
                    entry_id: animeId,
                    source: entrySource,
                    entry_type: "ANIME",
                    status: "CURRENT",
                    progress: episode
                });
                progressUpdated = true;
            } catch (e) { console.error("[MPV] Progress update failed", e); }
        };

        const loadingTimeout = setTimeout(() => { if (!socket.destroyed) socket.end(); }, 15000);

        socket.on('data', (data) => {
            const chunks = data.toString().split('\n');
            for (const chunk of chunks) {
                if (!chunk.trim()) continue;
                try {
                    const json = JSON.parse(chunk);

                    if (json.event === 'property-change' && json.name === 'duration' && typeof json.data === 'number') {
                        duration = json.data;
                        if (!titleChanged) {
                            socket.write(JSON.stringify({ command: ['set_property', 'force-media-title', mediaTitle] }) + '\n');

                            sendRPC(false);
                            titleChanged = true;
                            clearTimeout(loadingTimeout);
                        }
                    }

                    if (json.event === 'property-change' && json.name === 'time-pos' && typeof json.data === 'number') {
                        currentTime = json.data;

                        if (duration > 0 && !progressUpdated && (currentTime / duration) >= 0.8) {
                            updateProgress();
                        }
                    }

                    if (json.event === 'property-change' && json.name === 'pause') {
                        isPaused = json.data === true;
                        sendRPC(isPaused);

                    }

                    if (json.event === 'seek') {

                        setTimeout(() => sendRPC(isPaused), 100);
                    }

                    if (json.event === 'end-file' || json.event === 'shutdown') {
                        sendRPC(true);
                        if (!socket.destroyed) socket.destroy();
                    }

                } catch (err) {}
            }
        });

        const commands = [
            { command: ['observe_property', 1, 'duration'] },
            { command: ['observe_property', 2, 'time-pos'] },
            { command: ['observe_property', 3, 'pause'] },
            { command: ['loadfile', proxyVideo, 'replace'] }
        ];

        commands.forEach(cmd => socket.write(JSON.stringify(cmd) + '\n'));

        subtitles.forEach((s: any, i: number) => {
            socket.write(JSON.stringify({
                command: [
                    'sub-add',
                    proxySubs[i],
                    'auto',
                    s.label || 'Subtitle',
                    s.srclang || ''
                ]
            }) + '\n');
        });

        return { success: true };
    } catch (e) {
        return { error: (e as Error).message };
    }
}