import { getConfig as loadConfig } from '../../shared/config.js';
import { queryOne, queryAll, run } from '../../shared/database.js';
import crypto from 'crypto';
import fs from "fs";
import { PathLike } from "node:fs";
import path from "path";
import { getAnimeById, searchAnimeLocal } from "../anime/anime.service";
import { getBookById, searchBooksAniList } from "../books/books.service";
import AdmZip from 'adm-zip';
import EPub from 'epub';

const MANGA_IMAGE_EXTS = ['.jpg', '.jpeg', '.png', '.webp'];
const MANGA_ARCHIVES = ['.cbz', '.cbr', '.zip'];
const NOVEL_EXTS = ['.epub', '.pdf', '.txt', '.md', '.docx', '.mobi'];

export async function resolveEntryMetadata(entry: any, type: string) {
    let metadata = null;
    let matchedId = entry.matched_id;

    if (!matchedId) {
        const query = entry.folder_name;

        const results = type === 'anime'
            ? await searchAnimeLocal(query)
            : await searchBooksAniList(query);

        let picked = null;

        if (type !== 'anime' && Array.isArray(results)) {
            console.log(type);
            if (entry.type === 'novels') {
                picked = results.find(r => r.format === 'NOVEL');
            } else if (entry.type === 'manga') {
                picked = results.find(r => r.format !== 'NOVEL');
            }
        }

        picked ??= results?.[0];

        if (picked?.id) {
            matchedId = picked.id;

            await run(
                `UPDATE local_entries
                 SET matched_id = ?, matched_source = 'anilist'
                 WHERE id = ?`,
                [matchedId, entry.id],
                'local_library'
            );
        }
    }

    if (matchedId) {
        metadata = type === 'anime'
            ? await getAnimeById(matchedId)
            : await getBookById(matchedId);
    }

    return {
        id: entry.id,
        type: entry.type,
        matched: !!matchedId,
        metadata
    };
}

export async function performLibraryScan(mode: 'full' | 'incremental' = 'incremental') {
    const config = loadConfig();

    if (!config.library) {
        throw new Error('NO_LIBRARY_CONFIGURED');
    }

    if (mode === 'full') {
        await run(`DELETE FROM local_files`, [], 'local_library');
        await run(`DELETE FROM local_entries`, [], 'local_library');
    }

    for (const [type, basePath] of Object.entries(config.library)) {
        if (!basePath || !fs.existsSync(<PathLike>basePath)) continue;

        const dirs = fs.readdirSync(<string>basePath, { withFileTypes: true }).filter(d => d.isDirectory());

        for (const dir of dirs) {
            const fullPath = path.join(<string>basePath, dir.name);
            const id = crypto.createHash('sha1').update(fullPath).digest('hex');
            const now = Date.now();

            const existing = await queryOne(`SELECT id FROM local_entries WHERE id = ?`, [id], 'local_library');

            if (existing) {
                await run(`UPDATE local_entries SET last_scan = ? WHERE id = ?`, [now, id], 'local_library');
                await run(`DELETE FROM local_files WHERE entry_id = ?`, [id], 'local_library');
            } else {
                await run(
                    `INSERT INTO local_entries (id, type, path, folder_name, last_scan) VALUES (?, ?, ?, ?, ?)`,
                    [id, type, fullPath, dir.name, now],
                    'local_library'
                );
            }

            const files = fs.readdirSync(fullPath, { withFileTypes: true })
                .filter(f =>
                    f.isFile() ||
                    (type === 'manga' && f.isDirectory())
                )
                .sort((a, b) => a.name.localeCompare(b.name));

            let unit = 1;

            for (const file of files) {
                await run(
                    `INSERT INTO local_files (id, entry_id, file_path, unit_number)
                     VALUES (?, ?, ?, ?)`,
                    [crypto.randomUUID(), id, path.join(fullPath, file.name), unit],
                    'local_library'
                );
                unit++;
            }
        }
    }

    return { status: 'OK' };
}

export async function getEntriesByType(type: string) {
    const entries = await queryAll(`SELECT * FROM local_entries WHERE type = ?`, [type], 'local_library');
    return await Promise.all(entries.map((entry: any) => resolveEntryMetadata(entry, type)));
}

export async function getEntryDetails(type: string, id: string) {
    const entry = await queryOne(
        `SELECT * FROM local_entries WHERE matched_id = ? AND type = ?`,
        [Number(id), type],
        'local_library'
    );

    if (!entry) {
        return null;
    }

    const [details, files] = await Promise.all([
        resolveEntryMetadata(entry, type),
        queryAll(
            `SELECT id, file_path, unit_number FROM local_files WHERE entry_id = ? ORDER BY unit_number ASC`,
            [id],
            'local_library'
        )
    ]);

    return { ...details, files };
}

export async function getFileForStreaming(id: string, unit: string) {
    const file = await queryOne(
        `SELECT file_path FROM local_files WHERE entry_id = ? AND unit_number = ?`,
        [id, unit],
        'local_library'
    );

    if (!file || !fs.existsSync(file.file_path)) {
        return null;
    }

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

export async function updateEntryMatch(id: string, type: string, source: string, matchedId: number | null) {
    const entry = await queryOne(
        `SELECT id FROM local_entries WHERE id = ? AND type = ?`,
        [id, type],
        'local_library'
    );

    if (!entry) {
        return null;
    }

    await run(
        `UPDATE local_entries
         SET matched_source = ?, matched_id = ?
         WHERE id = ?`,
        [source, matchedId, id],
        'local_library'
    );

    return { status: 'OK', matched: !!matchedId };
}

function isImageFolder(folderPath: string): boolean {
    if (!fs.existsSync(folderPath)) return false;
    if (!fs.statSync(folderPath).isDirectory()) return false;

    const files = fs.readdirSync(folderPath);
    return files.some(f => MANGA_IMAGE_EXTS.includes(path.extname(f).toLowerCase()));
}

export async function getEntryUnits(id: string) {
    const entry = await queryOne(
        `SELECT id, type, matched_id FROM local_entries WHERE matched_id = ?`,
        [Number(id)],
        'local_library'
    );

    if (!entry) {
        return null;
    }

    const files = await queryAll(
        `SELECT id, file_path, unit_number FROM local_files 
         WHERE entry_id = ? 
         ORDER BY unit_number ASC`,
        [entry.id],
        'local_library'
    );

    const units = files
        .map((file: any) => {
            const fileExt = path.extname(file.file_path).toLowerCase();
            const isDir = fs.existsSync(file.file_path) &&
                fs.statSync(file.file_path).isDirectory();

            if (entry.type === 'manga') {
                if (MANGA_ARCHIVES.includes(fileExt)) {
                    return {
                        id: file.id,
                        number: file.unit_number,
                        name: path.basename(file.file_path),
                        type: 'chapter',
                        format: fileExt.replace('.', ''),
                        path: file.file_path
                    };
                }

                if (isDir && isImageFolder(file.file_path)) {
                    return {
                        id: file.id,
                        number: file.unit_number,
                        name: path.basename(file.file_path),
                        type: 'chapter',
                        format: 'folder',
                        path: file.file_path
                    };
                }

                return null;
            }

            if (entry.type === 'novels') {
                if (NOVEL_EXTS.includes(fileExt)) {
                    return {
                        id: file.id,
                        number: file.unit_number,
                        name: path.basename(file.file_path),
                        type: 'chapter',
                        format: fileExt.replace('.', ''),
                        path: file.file_path
                    };
                }

                return null;
            }

            if (entry.type === 'anime') {
                return {
                    id: file.id,
                    number: file.unit_number,
                    name: path.basename(file.file_path),
                    type: 'episode',
                    format: fileExt.replace('.', ''),
                    path: file.file_path
                };
            }

            return null;
        })
        .filter(Boolean);

    return {
        entry_id: entry.id,
        matched_id: entry.matched_id,
        type: entry.type,
        total: units.length,
        units
    };
}

export async function getUnitManifest(unitId: string) {
    const file = await queryOne(
        `SELECT file_path FROM local_files WHERE id = ?`,
        [unitId],
        'local_library'
    );

    if (!file || !fs.existsSync(file.file_path)) {
        return null;
    }

    const ext = path.extname(file.file_path).toLowerCase();

    if (['.cbz', '.cbr', '.zip'].includes(ext)) {
        const zip = new AdmZip(file.file_path);

        const pages = zip.getEntries()
            .filter(e => !e.isDirectory && /\.(jpg|jpeg|png|webp)$/i.test(e.entryName))
            .sort((a, b) => a.entryName.localeCompare(b.entryName, undefined, { numeric: true }))
            .map((_, i) => ({
                id: i,
                url: `/api/library/${unitId}/resource/${i}`
            }));

        return {
            type: 'manga',
            format: 'archive',
            pages
        };
    }

    if (fs.statSync(file.file_path).isDirectory()) {
        const pages = fs.readdirSync(file.file_path)
            .filter(f => /\.(jpg|jpeg|png|webp)$/i.test(f))
            .sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))
            .map((_, i) => ({
                id: i,
                url: `/api/library/${unitId}/resource/${i}`
            }));

        return {
            type: 'manga',
            format: 'folder',
            pages
        };
    }

    if (ext === '.epub') {
        return {
            type: 'ln',
            format: 'epub',
            url: `/api/library/${unitId}/resource/epub`
        };
    }

    if (['.txt', '.md'].includes(ext)) {
        return {
            type: 'ln',
            format: 'text',
            url: `/api/library/${unitId}/resource/text`
        };
    }

    if (ext === '.pdf') {
        return {
            type: 'ln',
            format: 'pdf',
            url: `/api/library/${unitId}/resource/pdf`
        };
    }

    throw new Error('UNSUPPORTED_FORMAT');
}

export async function getUnitResource(unitId: string, resId: string) {
    const file = await queryOne(
        `SELECT file_path FROM local_files WHERE id = ?`,
        [unitId],
        'local_library'
    );

    if (!file) return null;

    const ext = path.extname(file.file_path).toLowerCase();

    if (['.cbz', '.zip', '.cbr'].includes(ext)) {
        const zip = new AdmZip(file.file_path);
        const images = zip.getEntries()
            .filter(e => !e.isDirectory && /\.(jpg|jpeg|png|webp)$/i.test(e.entryName))
            .sort((a, b) => a.entryName.localeCompare(b.entryName, undefined, { numeric: true }));

        const entry = images[Number(resId)];
        if (!entry) return null;

        return {
            type: 'image',
            data: entry.getData()
        };
    }

    if (fs.statSync(file.file_path).isDirectory()) {
        const images = fs.readdirSync(file.file_path)
            .filter(f => /\.(jpg|jpeg|png|webp)$/i.test(f))
            .sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));

        const img = images[Number(resId)];
        if (!img) return null;

        const imgPath = path.join(file.file_path, img);
        const stat = fs.statSync(imgPath);

        return {
            type: 'image',
            path: imgPath,
            size: stat.size
        };
    }

    if (ext === '.epub') {
        const html = await parseEpubToHtml(file.file_path);

        return {
            type: 'html',
            data: html
        };
    }

    if (['.txt', '.md'].includes(ext)) {
        const text = fs.readFileSync(file.file_path, 'utf8');

        return {
            type: 'html',
            data: `<div class="ln-content"><pre>${text}</pre></div>`
        };
    }

    return null;
}

function parseEpubToHtml(filePath: string): Promise<string> {
    return new Promise((resolve, reject) => {
        const epub = new EPub(filePath);

        epub.on('end', async () => {
            let html = '';

            for (const id of epub.flow.map(f => f.id)) {
                const chapter = await new Promise<string>((res, rej) => {
                    epub.getChapter(id, (err, text) => {
                        if (err) rej(err);
                        else res(text);
                    });
                });

                html += `<section class="ln-chapter">${chapter}</section>`;
            }

            resolve(html);
        });

        epub.on('error', reject);
        epub.parse();
    });
}