import {FastifyReply, FastifyRequest} from 'fastify';
import fs from 'fs';
import * as service from './local.service';
import * as downloadService from './download.service';
import * as streamingService from './streaming.service';

type ScanQuery = {
    mode?: 'full' | 'incremental';
};

type Params = {
    type: 'anime' | 'manga' | 'novels';
    id?: string;
};

type MatchBody = {
    source: 'anilist';
    matched_id: number | null;
};

type DownloadAnimeBody =
    | {
    anilist_id: number;
    episode_number: number;
    stream_url: string;
    is_master?: false;
    subtitles?: {
        language: string;
        url: string;
    }[];
    duration?: number;
    chapters?: {
        title: string;
        start_time: number;
        end_time: number;
    }[];
}
    | {
    anilist_id: number;
    episode_number: number;
    stream_url: string;
    duration?: number;
    is_master: true;
    variant: {
        resolution: string;
        bandwidth?: number;
        codecs?: string;
        playlist_url: string;
    };
    audio?: {
        group?: string;
        language?: string;
        name?: string;
        playlist_url: string;
    }[];
    subtitles?: {
        language: string;
        url: string;
    }[];
    chapters?: {
        title: string;
        start_time: number;
        end_time: number;
    }[];
};

type DownloadBookBody = {
    anilist_id: number;
    chapter_number: number;
    format: 'manga' | 'novel';
    content?: string;
    images?: Array<{
        index: number;
        url: string;
    }>;
};

export async function scanLibrary(request: FastifyRequest<{ Querystring: ScanQuery }>, reply: FastifyReply) {
    try {
        const mode = request.query.mode || 'incremental';
        return await service.performLibraryScan(mode);
    } catch (err: any) {
        if (err.message === 'NO_LIBRARY_CONFIGURED') {
            return reply.status(400).send({ error: 'NO_LIBRARY_CONFIGURED' });
        }
        return reply.status(500).send({ error: 'FAILED_TO_SCAN_LIBRARY' });
    }
}

export async function listEntries(request: FastifyRequest<{ Params: Params }>, reply: FastifyReply) {
    try {
        const { type } = request.params;
        return await service.getEntriesByType(type);
    } catch {
        return reply.status(500).send({ error: 'FAILED_TO_LIST_ENTRIES' });
    }
}

export async function getEntry(request: FastifyRequest<{ Params: Params }>, reply: FastifyReply) {
    try {
        const { type, id } = request.params as { type: string, id: string };
        const entry = await service.getEntryDetails(type, id);

        if (!entry) {
            return reply.status(404).send({ error: 'ENTRY_NOT_FOUND' });
        }

        return entry;
    } catch {
        return reply.status(500).send({ error: 'FAILED_TO_GET_ENTRY' });
    }
}

export async function streamUnit(request: FastifyRequest, reply: FastifyReply) {
    const { id, unit } = request.params as any;

    const fileInfo = await service.getFileForStreaming(id, unit);

    if (!fileInfo) {
        return reply.status(404).send({ error: 'FILE_NOT_FOUND' });
    }

    const { filePath, stat } = fileInfo;
    const range = request.headers.range;

    if (!range) {
        reply
            .header('Content-Length', stat.size)
            .header('Content-Type', 'video/mp4');
        return fs.createReadStream(filePath);
    }

    const parts = range.replace(/bytes=/, '').split('-');
    const start = Number(parts[0]);
    const end = parts[1] ? Number(parts[1]) : stat.size - 1;

    if (
        Number.isNaN(start) ||
        Number.isNaN(end) ||
        start < 0 ||
        start >= stat.size ||
        end < start ||
        end >= stat.size
    ) {
        return reply.status(416).send({ error: 'INVALID_RANGE' });
    }

    const contentLength = end - start + 1;

    reply
        .status(206)
        .header('Content-Range', `bytes ${start}-${end}/${stat.size}`)
        .header('Accept-Ranges', 'bytes')
        .header('Content-Length', contentLength)
        .header('Content-Type', 'video/mp4');

    return fs.createReadStream(filePath, { start, end });
}

export async function matchEntry(
    request: FastifyRequest<{ Body: MatchBody }>,
    reply: FastifyReply
) {
    const { id, type } = request.params as any;
    const { source, matched_id } = request.body;

    const result = await service.updateEntryMatch(id, type, source, matched_id);

    if (!result) {
        return reply.status(404).send({ error: 'ENTRY_NOT_FOUND' });
    }

    return result;
}

export async function getUnits(request: FastifyRequest<{ Params: Params }>, reply: FastifyReply) {
    try {
        const { id } = request.params as { id: string };
        const units = await service.getEntryUnits(id);

        if (!units) {
            return reply.status(404).send({ error: 'ENTRY_NOT_FOUND' });
        }

        return units;
    } catch (err) {
        console.error('Error getting units:', err);
        return reply.status(500).send({ error: 'FAILED_TO_GET_UNITS' });
    }
}

export async function getManifest(request: FastifyRequest, reply: FastifyReply) {
    const { unitId } = request.params as any;

    try {
        const manifest = await service.getUnitManifest(unitId);

        if (!manifest) {
            return reply.status(404).send({ error: 'FILE_NOT_FOUND' });
        }

        return manifest;
    } catch (err: any) {
        if (err.message === 'UNSUPPORTED_FORMAT') {
            return reply.status(400).send({ error: 'UNSUPPORTED_FORMAT' });
        }
        return reply.status(500).send({ error: 'FAILED_TO_GET_MANIFEST' });
    }
}

export async function getPage(request: FastifyRequest, reply: FastifyReply) {
    const { unitId, resId } = request.params as any;

    const resource = await service.getUnitResource(unitId, resId);

    if (!resource) {
        return reply.status(404).send();
    }

    if (resource.type === 'image') {
        if (resource.data) {
            return reply
                .header('Content-Type', 'image/jpeg')
                .send(resource.data);
        }

        if (resource.path && resource.size) {
            reply
                .header('Content-Length', resource.size)
                .header('Content-Type', 'image/jpeg');

            return fs.createReadStream(resource.path);
        }
    }

    if (resource.type === 'html') {
        return reply
            .header('Content-Type', 'text/html; charset=utf-8')
            .send(resource.data);
    }

    return reply.status(400).send();
}

function buildProxyUrl(rawUrl: string, headers: Record<string, string>) {
    const params = new URLSearchParams({ url: rawUrl });

    for (const [key, value] of Object.entries(headers)) {
        params.set(key.toLowerCase(), value);
    }

    return `http://localhost:54322/api/proxy?${params.toString()}`;
}

export async function downloadAnime(request: FastifyRequest<{ Body: DownloadAnimeBody }>, reply: FastifyReply) {
    try {
        const {
            anilist_id,
            episode_number,
            stream_url,
            duration,
            is_master,
            subtitles,
            chapters
        } = request.body;

        const clientHeaders = (request.body as any).headers || {};

        if (!anilist_id || !episode_number || !stream_url) {
            return reply.status(400).send({
                error: 'MISSING_REQUIRED_FIELDS',
                required: ['anilist_id', 'episode_number', 'stream_url']
            });
        }

        const proxyUrl = buildProxyUrl(stream_url, clientHeaders);
        console.log('Stream URL:', proxyUrl);

        const proxiedSubs = subtitles?.map(sub => ({
            ...sub,
            url: buildProxyUrl(sub.url, clientHeaders)
        }));

        const downloadParams: any = {
            anilistId: anilist_id,
            episodeNumber: episode_number,
            streamUrl: proxyUrl,
            subtitles: proxiedSubs,
            chapters,
            totalDuration: duration
        };

        if (is_master === true) {
            const { variant, audio } = request.body as any;

            if (!variant || !variant.playlist_url) {
                return reply.status(400).send({
                    error: 'MISSING_VARIANT',
                    message: 'variant with playlist_url is required when is_master is true'
                });
            }

            downloadParams.is_master = true;
            downloadParams.variant = {
                ...variant,
                playlist_url: buildProxyUrl(variant.playlist_url, clientHeaders)
            };

            if (audio && audio.length > 0) {
                downloadParams.audio = audio.map((a: any) => ({
                    ...a,
                    playlist_url: buildProxyUrl(a.playlist_url, clientHeaders)
                }));
            }

            console.log('Master playlist detected');
            console.log('Variant:', downloadParams.variant.resolution);
            console.log('Audio tracks:', downloadParams.audio?.length || 0);
        }

        const result = await downloadService.downloadAnimeEpisode(downloadParams);

        if (result.status === 'ALREADY_EXISTS') {
            return reply.status(409).send(result);
        }

        return result;
    } catch (err: any) {
        console.error('Error downloading anime:', err);

        if (err.message === 'METADATA_NOT_FOUND') {
            return reply.status(404).send({ error: 'ANIME_NOT_FOUND_IN_ANILIST' });
        }

        if (err.message === 'VARIANT_REQUIRED_FOR_MASTER') {
            return reply.status(400).send({ error: 'VARIANT_REQUIRED_FOR_MASTER' });
        }

        if (err.message === 'DOWNLOAD_FAILED') {
            return reply.status(500).send({ error: 'DOWNLOAD_FAILED', details: err.details });
        }

        return reply.status(500).send({ error: 'FAILED_TO_DOWNLOAD_ANIME' });
    }
}

export async function downloadBook(request: FastifyRequest<{ Body: DownloadBookBody }>, reply: FastifyReply) {
    try {
        const {
            anilist_id,
            chapter_number,
            format,
            content,
            images
        } = request.body;

        if (!anilist_id || !chapter_number || !format) {
            return reply.status(400).send({
                error: 'MISSING_REQUIRED_FIELDS',
                required: ['anilist_id', 'chapter_number', 'format']
            });
        }

        if (format === 'novel' && !content) {
            return reply.status(400).send({
                error: 'MISSING_CONTENT',
                message: 'content field is required for novels'
            });
        }

        if (format === 'manga' && (!images || images.length === 0)) {
            return reply.status(400).send({
                error: 'MISSING_IMAGES',
                message: 'images field is required for manga'
            });
        }

        const result = await downloadService.downloadBookChapter({
            anilistId: anilist_id,
            chapterNumber: chapter_number,
            format,
            content,
            images
        });

        if (result.status === 'ALREADY_EXISTS') {
            return reply.status(409).send(result);
        }

        return result;
    } catch (err: any) {
        console.error('Error downloading book:', err);

        if (err.message === 'METADATA_NOT_FOUND') {
            return reply.status(404).send({ error: 'BOOK_NOT_FOUND_IN_ANILIST' });
        }

        if (err.message === 'DOWNLOAD_FAILED') {
            return reply.status(500).send({ error: 'DOWNLOAD_FAILED', details: err.details });
        }

        return reply.status(500).send({ error: 'FAILED_TO_DOWNLOAD_BOOK' });
    }
}

export async function getDownloadStatus(request: FastifyRequest, reply: FastifyReply) {
    try {
        const downloads = downloadService.getActiveDownloads();
        const streams = streamingService.getActiveStreamsStatus();

        return {
            downloads: {
                total: downloads.length,
                active: downloads.filter(d => d.status === 'downloading').length,
                completed: downloads.filter(d => d.status === 'completed').length,
                failed: downloads.filter(d => d.status === 'failed').length,
                list: downloads
            },
            streams: {
                total: streams.length,
                active: streams.filter(s => !s.isComplete).length,
                completed: streams.filter(s => s.isComplete).length,
                list: streams
            }
        };
    } catch (err) {
        console.error('Error getting download status:', err);
        return reply.status(500).send({ error: 'FAILED_TO_GET_DOWNLOAD_STATUS' });
    }
}

export async function getAnimeStreamManifest(request: FastifyRequest, reply: FastifyReply) {
    try {
        const { type, id, unit } = request.params as any;

        if (type !== 'anime') {
            return reply.status(400).send({ error: 'ONLY_ANIME_SUPPORTED' });
        }

        const fileInfo = await service.getFileForStreaming(id, unit);

        if (!fileInfo) {
            return reply.status(404).send({ error: 'FILE_NOT_FOUND' });
        }

        const manifest = await streamingService.getStreamingManifest(fileInfo.filePath);

        if (!manifest) {
            return reply.status(500).send({ error: 'FAILED_TO_GENERATE_MANIFEST' });
        }

        return manifest;
    } catch (err: any) {
        console.error('Error getting stream manifest:', err);
        return reply.status(500).send({ error: 'FAILED_TO_GET_STREAM_MANIFEST' });
    }
}

export async function serveHLSFile(request: FastifyRequest, reply: FastifyReply) {
    try {
        const { hash, filename } = request.params as any;

        const file = await streamingService.getHLSFile(hash, filename);

        if (!file) {
            return reply.status(404).send({ error: 'FILE_NOT_FOUND' });
        }

        const contentType = filename.endsWith('.m3u8')
            ? 'application/vnd.apple.mpegurl'
            : filename.endsWith('.vtt')
                ? 'text/vtt'
                : 'video/mp2t';

        reply
            .header('Content-Type', contentType)
            .header('Content-Length', file.stat.size)
            .header('Access-Control-Allow-Origin', '*');

        return fs.createReadStream(file.path);
    } catch (err) {
        console.error('Error serving HLS file:', err);
        return reply.status(500).send({ error: 'FAILED_TO_SERVE_HLS_FILE' });
    }
}