import { Chapter, Cover, Manga, Tag } from '../src/index';
import type Relationship from '../src/internal/Relationship';
import { ensureLogin, expectEqualIds } from './testutil';

beforeAll(async () => {
    await ensureLogin();
});

test('getByQuery() and search()', async () => {
    const query = { title: 'isekai' };
    const queriedManga = await Manga.getByQuery(query);
    const searchedManga = await Manga.search(query);
    expect(queriedManga).not.toBeNull();
    expect(searchedManga.length).toBeGreaterThan(0);
    expect(searchedManga[0]).toBeInstanceOf(Manga);
    expect(queriedManga!.id).toEqual(searchedManga[0].id);
});

test('getMultiple() and search() with a high limit', async () => {
    const searchManga = await Manga.search({ limit: 200 });
    expect(searchManga.length).toBeGreaterThan(0);
    const searchIds = searchManga.map((m) => m.id);
    const multipleManga = await Manga.getMultiple(searchIds);
    expectEqualIds(multipleManga, searchManga);
});

test('get() and search()', async () => {
    const searchManga = await Manga.search({ limit: 1 });
    expect(searchManga.length).toEqual(1);
    const mangaId = searchManga[0].id;
    const manga = await Manga.get(mangaId);
    expect(manga).toEqual(searchManga[0]);
    expect(manga).toBeInstanceOf(Manga);
});

test('getRandom() and localizations', async () => {
    const manga = await Manga.getRandom();
    expect(manga).toBeInstanceOf(Manga);
    expect(manga.localTitle).toEqual(manga.title.localString);
    expect(manga.localDescription).toEqual(manga.description.localString);
    expect(manga.localAltTitles).toEqual(manga.altTitles.map((t) => t.localString));
});

test('getFeed()', async () => {
    const manga = await Manga.getByQuery({ hasAvailableChapters: true });
    expect(manga).not.toBeNull();
    const feed = await manga!.getFeed();
    expect(feed.length).toBeGreaterThan(0);
    expect(feed[0].manga.id).toEqual(manga!.id);
    expect(feed[0]).toBeInstanceOf(Chapter);
});

test('getCovers()', async () => {
    const manga = await Manga.getRandom();
    const covers = await manga.getCovers();
    expect(covers.length).toBeGreaterThan(0);
    expect(covers[0]).toBeInstanceOf(Cover);
    expect(covers[0].manga.id).toEqual(manga.id);
});

test('getRelations()', async () => {
    // Find a manga with the most relations
    const searchedManga = await Manga.search({ includedTags: [await Tag.getByName('Fan Colored')], limit: 100 });
    searchedManga.sort(
        (a, b) => Object.values(b.relatedManga).flat().length - Object.values(a.relatedManga).flat().length,
    );
    const manga = searchedManga[0];
    expect(manga).toBeDefined();

    // Check if the requested relations are cached and that they match the given relations
    const requestedRelations = await manga!.getRelations(true);
    const givenRelations = manga!.relatedManga as Record<string, Relationship<Manga>[]>;
    for (const [relType, relatedManga] of Object.entries(requestedRelations)) {
        for (const related of relatedManga) {
            // Sometimes the related manga don't exist, so if they're not cached, resolving should cause an error
            if (!related.cached) {
                expect(related.resolve()).rejects.not.toBeNull();
            }
        }

        const ids1 = relatedManga.map((m) => m.id);
        const ids2 = givenRelations[relType].map((m) => m.id);
        ids1.sort();
        ids2.sort();
        expect(ids1).toEqual(ids2);
    }
});

test('getStatistics()', async () => {
    const manga = await Manga.getRandom();
    const stats = await manga.getStatistics();
    expect(stats).toBeDefined();
});

test('getAggregate()', async () => {
    const manga = await Manga.getByQuery({ hasAvailableChapters: true, order: { followedCount: 'desc' } });
    expect(manga).not.toBeNull();

    // Get chapter to look for in aggregate
    const feed = await manga!.getFeed({ limit: 1 });
    const testChapter = feed[0];
    expect(testChapter).toBeDefined();
    const testGroup = testChapter.groups[0].id;
    const testLang = testChapter.translatedLanguage;
    expect(testGroup).toBeDefined();
    expect(testLang).toBeDefined();

    const aggregate = await manga!.getAggregate([testGroup], [testLang]);
    const chapterIds: string[] = Object.values(aggregate).flatMap((vol) =>
        Object.values(vol.chapters).map((c) => c.id),
    );
    expect(chapterIds).toContain(testChapter.id);
});

test('getTotalSearchResults()', async () => {
    const amount = await Manga.getTotalSearchResults({ title: 'isekai' });
    expect(amount).toBeGreaterThan(0);
});

test('getFollowedManga() and getReadChapters()', async () => {
    const followedManga = await Manga.getFollowedManga({ limit: 101 });
    if (followedManga.length === 0) {
        console.warn('No followed manga found, skipping test');
        return;
    }
    expect(followedManga[0]).toBeInstanceOf(Manga);

    const chapters = await Manga.getReadChapters(followedManga.map((m) => m.id));
    const readChapters = Object.values(chapters);
    readChapters.sort((a, b) => b.length - a.length)[0];
    if (readChapters[0].length === 0) {
        console.warn('No read chapters found, skipping test');
        return;
    }

    const mostReadManga = await readChapters[0][0].manga.resolve();
    const mostReadMangaChapters = await mostReadManga.getReadChapters();
    expect(mostReadMangaChapters.map((m) => m.id).sort()).toEqual(readChapters[0].map((m) => m.id).sort());
});

test('getUserRating() and getRandom()', async () => {
    const manga = await Manga.getRandom();
    expect(manga).toBeInstanceOf(Manga);
    const rating = await manga.getUserRating();
    if (rating !== null) {
        expect(typeof rating).toBe('number');
    }
});

test('getFollowedFeed()', async () => {
    const feed = await Manga.getFollowedFeed();
    for (const chapter of feed) {
        expect(chapter).toBeInstanceOf(Chapter);
    }
});

test('getReadingStatus() and getAllReadingStatus()', async () => {
    const statuses = await Manga.getAllReadingStatus();
    expect(statuses).toBeDefined();

    const markedMangaIds = Object.keys(statuses);
    if (markedMangaIds.length === 0) {
        console.warn('No reading statuses found, skipping test');
        return;
    }

    const mangaId = markedMangaIds[0];
    const manga = await Manga.get(mangaId); // Inefficient, but it tests 2 functions at once
    const mangaStatus = await manga.getReadingStatus();
    expect(statuses[mangaId]).toEqual(mangaStatus);
});
