import { getCachedExtension, cacheExtension, getCache, setCache, getExtensionTitle } from '../../shared/queries';
import { queryOne, queryAll, run } from '../../shared/database';
import { getAllExtensions, getBookExtensionsMap } from '../../shared/extensions';
import { Book, Extension, ChapterWithProvider, ChapterContent } from '../types';

const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
const ANILIST_URL = "https://graphql.anilist.co";

async function fetchAniList(query: string, variables: any) {
    const res = await fetch(ANILIST_URL, {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            "Accept": "application/json"
        },
        body: JSON.stringify({ query, variables })
    });

    if (!res.ok) {
        throw new Error(`AniList error ${res.status}`);
    }

    const json = await res.json();
    return json?.data;
}
const MEDIA_FIELDS = `
  id
  title {
    romaji
    english
    native
    userPreferred
  }
  type
  format
  status
  description
  startDate { year month day }
  endDate { year month day }
  season
  seasonYear
  episodes
  chapters
  volumes
  duration
  genres
  synonyms
  averageScore
  popularity
  favourites
  isAdult
  siteUrl
  coverImage {
    extraLarge
    large
    medium
    color
  }
  bannerImage
  updatedAt
`;

export async function getBookById(id: string | number): Promise<Book | { error: string }> {
    const row = await queryOne(
        "SELECT full_data FROM books WHERE id = ?",
        [id]
    );

    if (row) {
        return JSON.parse(row.full_data);
    }

    try {
        console.log(`[Book] Local miss for ID ${id}, fetching live...`);

        const query = `
        query ($id: Int) {
            Media(id: $id, type: MANGA) {
                id idMal title { romaji english native userPreferred } type format status description
                startDate { year month day } endDate { year month day } season seasonYear seasonInt
                episodes duration chapters volumes countryOfOrigin isLicensed source hashtag
                trailer { id site thumbnail } updatedAt coverImage { extraLarge large medium color }
                bannerImage genres synonyms averageScore meanScore popularity isLocked trending favourites
                tags { id name description category rank isGeneralSpoiler isMediaSpoiler isAdult userId }
                relations { edges { relationType node { id title { romaji } } } }
                characters(page: 1, perPage: 10) { nodes { id name { full } } }
                studios { nodes { id name isAnimationStudio } }
                isAdult nextAiringEpisode { airingAt timeUntilAiring episode }
                externalLinks { url site } 
                rankings { id rank type format year season allTime context }
            }
        }`;

        const response = await fetch('https://graphql.anilist.co', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            },
            body: JSON.stringify({
                query,
                variables: { id: parseInt(id.toString()) }
            })
        });

        const data = await response.json();

        if (data?.data?.Media) {
            const media = data.data.Media;

            const insertSql = `
                INSERT INTO books (id, title, updatedAt, full_data)
                VALUES (?, ?, ?, ?)
                ON CONFLICT(id) DO UPDATE SET
                    title = EXCLUDED.title,
                    updatedAt = EXCLUDED.updatedAt,
                    full_data = EXCLUDED.full_data;
            `;

            await run(insertSql, [
                media.id,
                media.title?.userPreferred || media.title?.romaji || media.title?.english || null,
                media.updatedAt || Math.floor(Date.now() / 1000),
                JSON.stringify(media)
            ]);

            return media;
        }
    } catch (e) {
        console.error("Fetch error:", e);
    }

    return { error: "Book not found" };
}

export async function refreshTrendingBooks(): Promise<void> {
    const query = `
      query {
        Page(page: 1, perPage: 10) {
          media(type: MANGA, sort: TRENDING_DESC) { ${MEDIA_FIELDS} }
        }
      }
    `;

    const data = await fetchAniList(query, {});
    const list = data?.Page?.media || [];
    const now = Math.floor(Date.now() / 1000);

    await queryOne("DELETE FROM trending_books");

    let rank = 1;
    for (const book of list) {
        await queryOne(
            "INSERT INTO trending_books (rank, id, full_data, updated_at) VALUES (?, ?, ?, ?)",
            [rank++, book.id, JSON.stringify(book), now]
        );
    }
}

export async function refreshPopularBooks(): Promise<void> {
    const query = `
      query {
        Page(page: 1, perPage: 10) {
          media(type: MANGA, sort: POPULARITY_DESC) { ${MEDIA_FIELDS} }
        }
      }
    `;

    const data = await fetchAniList(query, {});
    const list = data?.Page?.media || [];
    const now = Math.floor(Date.now() / 1000);

    await queryOne("DELETE FROM popular_books");

    let rank = 1;
    for (const book of list) {
        await queryOne(
            "INSERT INTO popular_books (rank, id, full_data, updated_at) VALUES (?, ?, ?, ?)",
            [rank++, book.id, JSON.stringify(book), now]
        );
    }
}

export async function getTrendingBooks(): Promise<Book[]> {
    const rows = await queryAll(
        "SELECT full_data FROM trending_books ORDER BY rank ASC LIMIT 10"
    );
    return rows.map((r: { full_data: string; }) => JSON.parse(r.full_data));
}

export async function getPopularBooks(): Promise<Book[]> {
    const rows = await queryAll(
        "SELECT full_data FROM popular_books ORDER BY rank ASC LIMIT 10"
    );
    return rows.map((r: { full_data: string; }) => JSON.parse(r.full_data));
}

export async function searchBooksLocal(query: string): Promise<Book[]> {
    if (!query || query.length < 2) {
        return [];
    }

    const sql = `SELECT full_data FROM books WHERE full_data LIKE ? LIMIT 50`;
    const rows = await queryAll(sql, [`%${query}%`]);

    const results: Book[] = rows.map((row: { full_data: string; }) => JSON.parse(row.full_data));

    const clean = results.filter(book => {
        const searchTerms = [
            book.title.english,
            book.title.romaji,
            book.title.native,
            ...(book.synonyms || [])
        ].filter(Boolean).map(t => t!.toLowerCase());

        return searchTerms.some(term => term.includes(query.toLowerCase()));
    });

    return clean.slice(0, 10);
}

export async function searchBooksAniList(query: string): Promise<Book[]> {
    const gql = `
    query ($search: String) {
        Page(page: 1, perPage: 5) {
            media(search: $search, type: MANGA, isAdult: false) {
                id title { romaji english native } 
                coverImage { extraLarge large } 
                bannerImage description averageScore format 
                seasonYear startDate { year }
            }
        }
    }`;

    const response = await fetch('https://graphql.anilist.co', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
        body: JSON.stringify({ query: gql, variables: { search: query } })
    });

    const liveData = await response.json();

    if (liveData.data && liveData.data.Page.media.length > 0) {
        return liveData.data.Page.media;
    }

    return [];
}

export async function getBookInfoExtension(ext: Extension | null, id: string): Promise<any[]> {
    if (!ext) return [];

    const extName = ext.constructor.name;

    const cached = await getCachedExtension(extName, id);
    if (cached) {
        try {
            return JSON.parse(cached.metadata);
        } catch {}
    }

    if (ext.type === 'book-board' && ext.getMetadata) {
        try {
            const info = await ext.getMetadata(id);

            if (info) {
                const normalized = {
                    id: info.id ?? id,
                    title: info.title ?? "",
                    format: info.format ?? "",
                    score: typeof info.score === "number" ? info.score : null,
                    genres: Array.isArray(info.genres) ? info.genres : [],
                    status: info.status ?? "",
                    published: info.published ?? "",
                    summary: info.summary ?? "",
                    chapters: Number.isFinite(info.chapters) ? info.chapters : 1,
                    image: typeof info.image === "string" ? info.image : ""
                };

                await cacheExtension(extName, id, normalized.title, normalized);
                return [normalized];
            }
        } catch (e) {
            console.error(`Extension getInfo failed:`, e);
        }
    }

    return [];
}

export async function searchBooksInExtension(ext: Extension | null, name: string, query: string): Promise<Book[]> {
    if (!ext) return [];

    if ((ext.type === 'book-board') && ext.search) {

        try {
            console.log(`[${name}] Searching for book: ${query}`);

            const matches = await ext.search({
                query: query,
                media: {
                    romajiTitle: query,
                    englishTitle: query,
                    startDate: { year: 0, month: 0, day: 0 }
                }
            });

            if (matches?.length) {
                return matches.map(m => ({
                    id: m.id,
                    extensionName: name,
                    title: { romaji: m.title, english: m.title, native: null },
                    coverImage: { large: m.image || '' },
                    averageScore: m.rating || m.score || null,
                    format: m.format,
                    seasonYear: null,
                    isExtensionResult: true
                }));
            }

        } catch (e) {
            console.error(`Extension search failed for ${name}:`, e);
        }
    }

    return [];
}

async function fetchBookMetadata(id: string): Promise<Book | null> {
    try {
        const query = `query ($id: Int) {
            Media(id: $id, type: MANGA) {
                title { romaji english }
                startDate { year month day }
            }
        }`;

        const res = await fetch('https://graphql.anilist.co', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ query, variables: { id: parseInt(id) } })
        });

        const d = await res.json();
        return d.data?.Media || null;
    } catch (e) {
        console.error("Failed to fetch book metadata:", e);
        return null;
    }
}

async function searchChaptersInExtension(ext: Extension, name: string, searchTitle: string, search: boolean, origin: string): Promise<ChapterWithProvider[]> {
    const cacheKey = `chapters:${name}:${origin}:${search ? "search" : "id"}:${searchTitle}`;
    const cached = await getCache(cacheKey);

    if (cached) {
        const isExpired = Date.now() - cached.created_at > CACHE_TTL_MS;

        if (!isExpired) {
            console.log(`[${name}] Chapters cache hit for: ${searchTitle}`);
            try {
                return JSON.parse(cached.result) as ChapterWithProvider[];
            } catch (e) {
                console.error(`[${name}] Error parsing cached chapters:`, e);
            }
        } else {
            console.log(`[${name}] Chapters cache expired for: ${searchTitle}`);
        }
    }

    try {
        console.log(`[${name}] Searching chapters for: ${searchTitle}`);

        let mediaId: string;
        if (search) {
            const matches = await ext.search!({
                query: searchTitle,
                media: {
                    romajiTitle: searchTitle,
                    englishTitle: searchTitle,
                    startDate: { year: 0, month: 0, day: 0 }
                }
            });

            const best = matches?.[0];

            if (!best) { return [] }

            mediaId = best.id;

        } else {
            const match = await ext.getMetadata(searchTitle);
            mediaId = match.id;
        }

        const chaps = await ext.findChapters!(mediaId);

        if (!chaps?.length){
            return [];
        }

        console.log(`[${name}] Found ${chaps.length} chapters.`);
        const result: ChapterWithProvider[] = chaps.map((ch) => ({
            id: ch.id,
            number: parseFloat(ch.number.toString()),
            title: ch.title,
            date: ch.releaseDate,
            provider: name,
            index: ch.index
        }));

        await setCache(cacheKey, result, CACHE_TTL_MS);
        return result;

    } catch (e) {
        const error = e as Error;
        console.error(`Failed to fetch chapters from ${name}:`, error.message);
        return [];
    }
}

export async function getChaptersForBook(id: string, ext: Boolean, onlyProvider?: string): Promise<{ chapters: ChapterWithProvider[] }> {
    let bookData: Book | null = null;
    let searchTitle: string = "";

    if (!ext) {
        const result = await getBookById(id);
        if (!result || "error" in result) return { chapters: [] }
        bookData = result;
        const titles = [bookData.title.english, bookData.title.romaji].filter(Boolean) as string[];
        searchTitle = titles[0];
    }
    const bookExtensions = getBookExtensionsMap();

    let extension;
    if (!searchTitle) {
        for (const [name, ext] of bookExtensions) {
            const title = await getExtensionTitle(name, id)
            if (title){
                searchTitle = title;
                extension = name;
            }
        }
    }

    const allChapters: any[] = [];
    let exts = "anilist";
    if (ext) exts = "ext";

    for (const [name, ext] of bookExtensions) {
        if (onlyProvider && name !== onlyProvider) continue;
        if (name == extension) {
            const chapters = await searchChaptersInExtension(ext, name, id, false, exts);
            allChapters.push(...chapters);
        } else {
            const chapters = await searchChaptersInExtension(ext, name, searchTitle, true, exts);
            allChapters.push(...chapters);
        }
    }

    return {
        chapters: allChapters.sort((a, b) => Number(a.number) - Number(b.number))
    };
}

export async function getChapterContent(bookId: string, chapterIndex: string, providerName: string, source: string): Promise<ChapterContent> {
    const extensions = getAllExtensions();
    const ext = extensions.get(providerName);

    if (!ext) {
        throw new Error("Provider not found");
    }

    const contentCacheKey = `content:${providerName}:${source}:${bookId}:${chapterIndex}`;
    const cachedContent = await getCache(contentCacheKey);

    if (cachedContent) {
        const isExpired = Date.now() - cachedContent.created_at > CACHE_TTL_MS;

        if (!isExpired) {
            console.log(`[${providerName}] Content cache hit for Book ID ${bookId}, Index ${chapterIndex}`);
            try {
                return JSON.parse(cachedContent.result) as ChapterContent;
            } catch (e) {
                console.error(`[${providerName}] Error parsing cached content:`, e);

            }
        } else {
            console.log(`[${providerName}] Content cache expired for Book ID ${bookId}, Index ${chapterIndex}`);
        }
    }

    const isExternal = source !== 'anilist';
    const chapterList = await getChaptersForBook(bookId, isExternal, providerName);

    if (!chapterList?.chapters || chapterList.chapters.length === 0) {
        throw new Error("Chapters not found");
    }

    const providerChapters = chapterList.chapters.filter(c => c.provider === providerName);
    const index = parseInt(chapterIndex, 10);

    if (Number.isNaN(index)) {
        throw new Error("Invalid chapter index");
    }

    if (!providerChapters[index]) {
        throw new Error("Chapter index out of range");
    }

    const selectedChapter = providerChapters[index];

    const chapterId = selectedChapter.id;
    const chapterTitle = selectedChapter.title || null;
    const chapterNumber = typeof selectedChapter.number === 'number' ? selectedChapter.number : index;

    try {
        if (!ext.findChapterPages) {
            throw new Error("Extension doesn't support findChapterPages");
        }

        let contentResult: ChapterContent;

        if (ext.mediaType === "manga") {
            const pages = await ext.findChapterPages(chapterId);
            contentResult = {
                type: "manga",
                chapterId,
                title: chapterTitle,
                number: chapterNumber,
                provider: providerName,
                pages
            };
        } else if (ext.mediaType === "ln") {
            const content = await ext.findChapterPages(chapterId);
            contentResult = {
                type: "ln",
                chapterId,
                title: chapterTitle,
                number: chapterNumber,
                provider: providerName,
                content
            };
        } else {
            throw new Error("Unknown mediaType");
        }

        await setCache(contentCacheKey, contentResult, CACHE_TTL_MS);

        return contentResult;

    } catch (err) {
        const error = err as Error;
        console.error(`[Chapter] Error loading from ${providerName}:`, error.message);
        throw err;
    }
}