import { getCache, setCache, getCachedExtension, cacheExtension, getExtensionTitle } from '../../shared/queries';
import { queryAll, queryOne } from '../../shared/database';
import {Anime, Episode, Extension, StreamData} from '../types';

const CACHE_TTL_MS = 24 * 60 * 60 * 1000;

const ANILIST_URL = "https://graphql.anilist.co";

const MEDIA_FIELDS = `
  id
  idMal
  title { romaji english native userPreferred }
  type
  format
  status
  description
  startDate { year month day }
  endDate { year month day }
  season
  seasonYear
  episodes
  duration
  chapters
  volumes
  countryOfOrigin
  isLicensed
  source
  hashtag
  trailer { id site thumbnail }
  updatedAt
  coverImage { extraLarge large medium color }
  bannerImage
  genres
  synonyms
  averageScore
  popularity
  isLocked
  trending
  favourites
  isAdult
  siteUrl
  tags { id name description category rank isGeneralSpoiler isMediaSpoiler isAdult }
  relations {
      edges {
        relationType
        node {
          id
          title { romaji english }
          type
          format
          status
          coverImage { medium large color }
          bannerImage
          season
          seasonYear
        }
      }
    }
  studios {
    edges {
      isMain
      node { id name isAnimationStudio }
    }
  }
  nextAiringEpisode { airingAt timeUntilAiring episode }
  externalLinks { id url site type language color icon notes }
  rankings { id rank type format year season allTime context }
  stats {
    scoreDistribution { score amount }
    statusDistribution { status amount }
  }
  recommendations(perPage: 7, sort: RATING_DESC) {
    nodes {
      mediaRecommendation {
        id
        title { romaji }
        coverImage { medium large}
        format
        type
      }
    }
  }
  characters(perPage: 12, sort: [ROLE, RELEVANCE]) {
    edges {
      role
      node {
        id
        name { full native }
        image { medium large }
      }
      voiceActors {
        id
        name { full }
        language
        image { medium }
      }
    }
  }
`;

export async function refreshTrendingAnime(): Promise<void> {
    const query = `
      query {
        Page(page: 1, perPage: 10) {
          media(type: ANIME, 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");

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

export async function refreshTopAiringAnime(): Promise<void> {
    const query = `
      query {
        Page(page: 1, perPage: 10) {
          media(type: ANIME, status: RELEASING, sort: SCORE_DESC) { ${MEDIA_FIELDS} }
        }
      }
    `;

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

    await queryOne("DELETE FROM top_airing");

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

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

    const json = await res.json();
    return json?.data;
}

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

    if (row) {
        const cached = JSON.parse(row.full_data);

        if (cached?.characters?.edges?.length) {
            return cached;
        }

        await queryOne(
            "DELETE FROM anime WHERE id = ?",
            [id]
        );
    }


    const query = `
    query ($id: Int) {
      Media(id: $id, type: ANIME) { ${MEDIA_FIELDS} }
    }
  `;

    const data = await fetchAniList(query, { id: Number(id) });
    if (!data?.Media) return { error: "Anime not found" };

    await queryOne(
        "INSERT INTO anime (id, title, updatedAt, full_data) VALUES (?, ?, ?, ?)",
        [
            data.Media.id,
            data.Media.title?.english || data.Media.title?.romaji,
            data.Media.updatedAt || 0,
            JSON.stringify(data.Media)
        ]
    );

    return data.Media;
}

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

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

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

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

    const localResults: Anime[] = rows
        .map((r: { full_data: string }) => JSON.parse(r.full_data))
        .filter((anime: { title: { english: any; romaji: any; native: any; }; synonyms: any; }) => {
            const q = query.toLowerCase();
            const titles = [
                anime.title?.english,
                anime.title?.romaji,
                anime.title?.native,
                ...(anime.synonyms || [])
            ]
                .filter(Boolean)
                .map(t => t!.toLowerCase());

            return titles.some(t => t.includes(q));
        })
        .slice(0, 10);

    if (localResults.length >= 5) {
        return localResults;
    }

    const gql = `
    query ($search: String) {
      Page(page: 1, perPage: 10) {
        media(type: ANIME, search: $search) { ${MEDIA_FIELDS} }
      }
    }
  `;

    const data = await fetchAniList(gql, { search: query });
    const remoteResults: Anime[] = data?.Page?.media || [];

    for (const anime of remoteResults) {
        await queryOne(
            "INSERT OR IGNORE INTO anime (id, title, updatedAt, full_data) VALUES (?, ?, ?, ?)",
            [
                anime.id,
                anime.title?.english || anime.title?.romaji,
                anime.updatedAt || 0,
                JSON.stringify(anime)
            ]
        );
    }

    const merged = [...localResults];

    for (const anime of remoteResults) {
        if (!merged.find(a => a.id === anime.id)) {
            merged.push(anime);
        }
        if (merged.length >= 10) break;
    }

    return merged;
}

export async function getAnimeInfoExtension(ext: Extension | null, id: string): Promise<Anime | { error: string }> {
    if (!ext) return { error: "not found" };

    const extName = ext.constructor.name;

    const cached = await getCachedExtension(extName, id);
    if (cached) {
        try {
            console.log(`[${extName}] Metadata cache hit for ID: ${id}`);
            return JSON.parse(cached.metadata) as Anime;
        } catch {

        }
    }

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

            if (match) {
                const normalized: any = {
                    title: match.title ?? "Unknown",
                    summary: match.summary ?? "No summary available",
                    episodes: Number(match.episodes) || 0,
                    characters: Array.isArray(match.characters) ? match.characters : [],
                    season: match.season ?? null,
                    status: match.status ?? "Unknown",
                    studio: match.studio ?? "Unknown",
                    score: Number(match.score) || 0,
                    year: match.year ?? null,
                    genres: Array.isArray(match.genres) ? match.genres : [],
                    image: match.image ?? ""
                };

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

    return { error: "not found" };
}

export async function searchAnimeInExtension(ext: Extension | null, name: string, query: string) {
    if (!ext) return [];

    if (ext.type === 'anime-board' && ext.search) {
        try {
            console.log(`[${name}] Searching for anime: ${query}`);
            const matches = await ext.search({
                query: query,
                media: {
                    romajiTitle: query,
                    englishTitle: query,
                    startDate: { year: 0, month: 0, day: 0 }
                }
            });

            if (matches && matches.length > 0) {
                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: 'ANIME',
                    seasonYear: null,
                    isExtensionResult: true,
                }));
            }
        } catch (e) {
            console.error(`Extension search failed for ${name}:`, e);
        }
    }

    return [];
}

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

    const cacheKey = `anime:episodes:${name}:${query}`;
    const cached = await getCache(cacheKey);

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

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

    if (ext.type === "anime-board" && ext.search && typeof ext.findEpisodes === "function") {
        try {
            const title = await getExtensionTitle(name, query);
            let mediaId: string;

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

                if (!matches || matches.length === 0) return [];

                const res = matches[0];
                if (!res?.id) return [];

                mediaId = res.id;

            } else {
                mediaId = query;
            }

            const chapterList = await ext.findEpisodes(mediaId);

            if (!Array.isArray(chapterList)) return [];

            const result: Episode[] = chapterList.map(ep => ({
                id: ep.id,
                number: ep.number,
                url: ep.url,
                title: ep.title
            }));

            await setCache(cacheKey, result, CACHE_TTL_MS);

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

    return [];
}

export async function getStreamData(extension: Extension, episode: string, id: string, source: string, server?: string, category?: string): Promise<StreamData> {
    const providerName = extension.constructor.name;

    const cacheKey = `anime:stream:${providerName}:${id}:${episode}:${server || 'default'}:${category || 'sub'}`;

    const cached = await getCache(cacheKey);

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

        if (!isExpired) {
            console.log(`[${providerName}] Stream data cache hit for episode ${episode}`);
            try {
                return JSON.parse(cached.result) as StreamData;
            } catch (e) {
                console.error(`[${providerName}] Error parsing cached stream data:`, e);
            }
        } else {
            console.log(`[${providerName}] Stream data cache expired for episode ${episode}`);
        }
    }

    if (!extension.findEpisodes || !extension.findEpisodeServer) {
        throw new Error("Extension doesn't support required methods");
    }
    let episodes;

    if (source === "anilist"){
        const anime: any = await getAnimeById(id)
        episodes = await searchEpisodesInExtension(extension, extension.constructor.name, anime.title.romaji);
    }
    else{
        episodes = await extension.findEpisodes(id);
    }
    const targetEp = episodes.find(e => e.number === parseInt(episode));

    if (!targetEp) {
        throw new Error("Episode not found");
    }

    const serverName = server || "default";
    const streamData = await extension.findEpisodeServer(targetEp, serverName);

    await setCache(cacheKey, streamData, CACHE_TTL_MS);
    return streamData;
}