import { afterEach, beforeEach, describe, expect, test } from "vitest";
import { databaseTimestampNow } from "~/utils/dates";
import { dbInsertUsers, dbReset, withUserId } from "~/utils/Test";
import * as ArtRepository from "../art/ArtRepository.server";
import * as CalendarRepository from "../calendar/CalendarRepository.server";
import * as ImageRepository from "./ImageRepository.server";

let imageCounter = 0;

const createImage = async ({
	submitterUserId,
	validatedAt = null,
}: {
	submitterUserId: number;
	validatedAt?: number | null;
}) => {
	imageCounter++;
	const url = `image-${submitterUserId}-${imageCounter}.png`;

	return ImageRepository.insert({ submitterUserId, url, validatedAt });
};

const createArtImage = async ({
	authorId,
	validatedAt = null,
}: {
	authorId: number;
	validatedAt?: number | null;
}) => {
	imageCounter++;
	return withUserId(authorId, () =>
		ArtRepository.insert({
			url: `art-${imageCounter}.png`,
			validatedAt,
			description: null,
			linkedUsers: [],
			tags: [],
		}),
	);
};

const createCalendarEvent = async (authorId: number, avatarImgId?: number) => {
	return CalendarRepository.create({
		isFullTournament: false,
		authorId,
		badges: [],
		bracketUrl: `https://example.com/bracket-${Date.now()}`,
		description: null,
		discordInviteCode: null,
		name: `Event ${Date.now()}`,
		organizationId: null,
		startTimes: [databaseTimestampNow()],
		tags: null,
		mapPickingStyle: "AUTO_SZ",
		bracketProgression: null,
		rules: null,
		avatarImgId,
	});
};

describe("findById", () => {
	beforeEach(async () => {
		imageCounter = 0;
		await dbInsertUsers(3);
	});

	afterEach(() => {
		dbReset();
	});

	test("finds image by id", async () => {
		const img = await createImage({ submitterUserId: 1 });

		const result = await ImageRepository.findById(img.id);

		expect(result).toBeDefined();
		expect(result?.tournamentId).toBeNull();
	});

	test("finds image with calendar event data", async () => {
		const img = await createImage({ submitterUserId: 1 });
		await createCalendarEvent(1, img.id);

		const result = await ImageRepository.findById(img.id);

		expect(result).toBeDefined();
		expect(result?.tournamentId).toBeDefined();
	});

	test("returns undefined for non-existent image", async () => {
		const result = await ImageRepository.findById(999);

		expect(result).toBeUndefined();
	});
});

describe("deleteImageById", () => {
	beforeEach(async () => {
		imageCounter = 0;
		await dbInsertUsers(2);
	});

	afterEach(() => {
		dbReset();
	});

	test("deletes image by id", async () => {
		const img = await createImage({ submitterUserId: 1 });

		await ImageRepository.deleteImageById(img.id);

		const result = await ImageRepository.findById(img.id);
		expect(result).toBeUndefined();
	});

	test("deletes associated art when deleting image", async () => {
		const art = await createArtImage({ authorId: 1, validatedAt: Date.now() });

		const artsBefore = await ArtRepository.findArtsByUserId(1);
		expect(artsBefore).toHaveLength(1);
		expect(artsBefore[0].id).toBe(art.id);

		await ImageRepository.deleteImageById(art.imgId);

		const result = await ImageRepository.findById(art.imgId);
		expect(result).toBeUndefined();

		const artsAfter = await ArtRepository.findArtsByUserId(1);
		expect(artsAfter).toHaveLength(0);
	});
});

describe("countUnvalidatedArt", () => {
	beforeEach(async () => {
		imageCounter = 0;
		await dbInsertUsers(3);
	});

	afterEach(() => {
		dbReset();
	});

	test("counts unvalidated art by author", async () => {
		await createArtImage({ authorId: 1 });
		await createArtImage({ authorId: 1 });

		const count = await ImageRepository.countUnvalidatedArt(1);

		expect(count).toBe(2);
	});

	test("does not count validated art", async () => {
		await createArtImage({ authorId: 1 });
		await createArtImage({ authorId: 1, validatedAt: Date.now() });

		const count = await ImageRepository.countUnvalidatedArt(1);

		expect(count).toBe(1);
	});

	test("returns 0 when author has no unvalidated art", async () => {
		const count = await ImageRepository.countUnvalidatedArt(1);

		expect(count).toBe(0);
	});
});

describe("countAllUnvalidated", () => {
	beforeEach(async () => {
		imageCounter = 0;
		await dbInsertUsers(3);
	});

	afterEach(() => {
		dbReset();
	});

	test("counts unvalidated images used in art", async () => {
		await createArtImage({ authorId: 1 });

		const count = await ImageRepository.countAllUnvalidated();

		expect(count).toBe(1);
	});

	test("counts unvalidated images used in calendar events", async () => {
		const img = await createImage({ submitterUserId: 1 });
		await createCalendarEvent(1, img.id);

		const count = await ImageRepository.countAllUnvalidated();

		expect(count).toBe(1);
	});

	test("does not count validated images", async () => {
		await createArtImage({ authorId: 1, validatedAt: Date.now() });

		const count = await ImageRepository.countAllUnvalidated();

		expect(count).toBe(0);
	});

	test("counts multiple unvalidated images across different types", async () => {
		await createArtImage({ authorId: 1 });

		const img = await createImage({ submitterUserId: 1 });
		await createCalendarEvent(1, img.id);

		const count = await ImageRepository.countAllUnvalidated();

		expect(count).toBe(2);
	});

	test("returns 0 when no unvalidated images exist", async () => {
		const count = await ImageRepository.countAllUnvalidated();

		expect(count).toBe(0);
	});
});

describe("countUnvalidatedBySubmitterUserId", () => {
	beforeEach(async () => {
		imageCounter = 0;
		await dbInsertUsers(3);
	});

	afterEach(() => {
		dbReset();
	});

	test("counts unvalidated images connected to art by submitter", async () => {
		await createArtImage({ authorId: 1 });
		await createArtImage({ authorId: 1 });

		const count = await ImageRepository.countUnvalidatedBySubmitterUserId(1);

		expect(count).toBe(2);
	});

	test("does not count orphan images not connected to anything", async () => {
		await createImage({ submitterUserId: 1 });

		const count = await ImageRepository.countUnvalidatedBySubmitterUserId(1);

		expect(count).toBe(0);
	});

	test("does not count validated images", async () => {
		await createArtImage({ authorId: 1, validatedAt: Date.now() });

		const count = await ImageRepository.countUnvalidatedBySubmitterUserId(1);

		expect(count).toBe(0);
	});

	test("does not count images from other submitters", async () => {
		await createArtImage({ authorId: 1 });
		await createArtImage({ authorId: 2 });

		const count = await ImageRepository.countUnvalidatedBySubmitterUserId(1);

		expect(count).toBe(1);
	});

	test("returns 0 when user has no unvalidated images", async () => {
		const count = await ImageRepository.countUnvalidatedBySubmitterUserId(1);

		expect(count).toBe(0);
	});
});

describe("validateImage", () => {
	beforeEach(async () => {
		imageCounter = 0;
		await dbInsertUsers(2);
	});

	afterEach(() => {
		dbReset();
	});

	test("marks image as validated", async () => {
		const img = await createImage({ submitterUserId: 1 });

		await ImageRepository.validateImage(img.id);

		const result = await ImageRepository.findById(img.id);
		expect(result).toBeDefined();
	});

	test("validated image is not included in unvalidated count", async () => {
		const art = await createArtImage({ authorId: 1 });

		const countBefore = await ImageRepository.countAllUnvalidated();
		expect(countBefore).toBe(1);

		await ImageRepository.validateImage(art.imgId);

		const countAfter = await ImageRepository.countAllUnvalidated();
		expect(countAfter).toBe(0);
	});
});

describe("unvalidatedImages", () => {
	beforeEach(async () => {
		imageCounter = 0;
		await dbInsertUsers(3);
	});

	afterEach(() => {
		dbReset();
	});

	test("fetches unvalidated images with submitter info", async () => {
		imageCounter++;
		const filename = `art-${imageCounter}.png`;
		await withUserId(1, () =>
			ArtRepository.insert({
				url: filename,
				validatedAt: null,
				description: null,
				linkedUsers: [],
				tags: [],
			}),
		);

		const result = await ImageRepository.unvalidatedImages();

		expect(result).toHaveLength(1);
		expect(result[0].submitterUserId).toBe(1);
		expect(result[0].username).toBe("user1");
		expect(result[0].url).toBe(`http://127.0.0.1:9000/sendou/${filename}`);
	});

	test("does not fetch validated images", async () => {
		await createArtImage({ authorId: 1, validatedAt: Date.now() });

		const result = await ImageRepository.unvalidatedImages();

		expect(result).toHaveLength(0);
	});

	test("fetches images from art and calendar events", async () => {
		await createArtImage({ authorId: 1 });
		await createArtImage({ authorId: 2 });

		const img = await createImage({ submitterUserId: 3 });
		await createCalendarEvent(3, img.id);

		const result = await ImageRepository.unvalidatedImages();

		expect(result).toHaveLength(3);
	});

	test("respects the max unvalidated images to show at once for approval limit constant", async () => {
		for (let i = 0; i < 10; i++) {
			await createArtImage({ authorId: 1 });
		}

		const result = await ImageRepository.unvalidatedImages();

		expect(result.length).toBe(5);
	});

	test("returns empty array when no unvalidated images exist", async () => {
		const result = await ImageRepository.unvalidatedImages();

		expect(result).toHaveLength(0);
	});
});
