import { afterEach, beforeEach, describe, expect, test } from "vitest";
import { db } from "~/db/sql";
import type { ModeShort, StageId } from "~/modules/in-game-lists/types";
import { dbInsertUsers, dbReset, withUserId } from "~/utils/Test";
import * as SQGroupRepository from "../sendouq/SQGroupRepository.server";
import * as SQMatchRepository from "./SQMatchRepository.server";

const createGroup = async (userIds: number[]) => {
	const groupResult = await SQGroupRepository.createGroup({
		status: "ACTIVE",
		userId: userIds[0],
	});

	for (let i = 1; i < userIds.length; i++) {
		await SQGroupRepository.addMember(groupResult.id, {
			userId: userIds[i],
			role: "REGULAR",
		});
	}

	return groupResult.id;
};

const createMatch = async (alphaGroupId: number, bravoGroupId: number) => {
	const match = await db
		.insertInto("GroupMatch")
		.values({
			alphaGroupId,
			bravoGroupId,
			chatCode: "test-chat-code",
		})
		.returningAll()
		.executeTakeFirstOrThrow();

	const mapList: Array<{
		mode: ModeShort;
		stageId: StageId;
	}> = [
		{ mode: "SZ", stageId: 1 },
		{ mode: "TC", stageId: 2 },
		{ mode: "RM", stageId: 3 },
		{ mode: "CB", stageId: 4 },
		{ mode: "SZ", stageId: 5 },
		{ mode: "TC", stageId: 6 },
		{ mode: "RM", stageId: 7 },
	];

	await db
		.insertInto("GroupMatchMap")
		.values(
			mapList.map((map, i) => ({
				matchId: match.id,
				index: i,
				mode: map.mode,
				stageId: map.stageId,
				source: "TIEBREAKER",
			})),
		)
		.execute();

	return match;
};

const fetchMapResults = async (matchId: number) => {
	return db
		.selectFrom("GroupMatchMap")
		.selectAll()
		.where("matchId", "=", matchId)
		.orderBy("index", "asc")
		.execute();
};

const fetchGroup = async (groupId: number) => {
	return db
		.selectFrom("Group")
		.selectAll()
		.where("id", "=", groupId)
		.executeTakeFirst();
};

const fetchSkills = async (matchId: number) => {
	return db
		.selectFrom("Skill")
		.selectAll()
		.where("groupMatchId", "=", matchId)
		.execute();
};

describe("lockMatchWithoutSkillChange", () => {
	beforeEach(async () => {
		await dbInsertUsers(8);
	});

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

	test("inserts dummy skill to lock match", async () => {
		const alphaGroupId = await createGroup([1, 2, 3, 4]);
		const bravoGroupId = await createGroup([5, 6, 7, 8]);
		const match = await createMatch(alphaGroupId, bravoGroupId);

		await SQMatchRepository.lockMatchWithoutSkillChange(match.id);

		const skills = await fetchSkills(match.id);
		expect(skills).toHaveLength(1);
		expect(skills[0].season).toBe(-1);
		expect(skills[0].mu).toBe(-1);
		expect(skills[0].sigma).toBe(-1);
		expect(skills[0].ordinal).toBe(-1);
		expect(skills[0].userId).toBeNull();
	});
});

describe("cancelMatch", () => {
	beforeEach(async () => {
		await dbInsertUsers(8);
	});

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

	test("first cancel report sets group inactive", async () => {
		const alphaGroupId = await createGroup([1, 2, 3, 4]);
		const bravoGroupId = await createGroup([5, 6, 7, 8]);
		const match = await createMatch(alphaGroupId, bravoGroupId);

		const result = await withUserId(1, () =>
			SQMatchRepository.cancelMatch({
				matchId: match.id,
			}),
		);

		expect(result.status).toBe("CANCEL_REPORTED");
		expect(result.shouldRefreshCaches).toBe(false);

		const alphaGroup = await fetchGroup(alphaGroupId);
		expect(alphaGroup?.status).toBe("INACTIVE");
	});

	test("matching cancel confirms and locks match", async () => {
		const alphaGroupId = await createGroup([1, 2, 3, 4]);
		const bravoGroupId = await createGroup([5, 6, 7, 8]);
		const match = await createMatch(alphaGroupId, bravoGroupId);

		await withUserId(1, () =>
			SQMatchRepository.cancelMatch({
				matchId: match.id,
			}),
		);

		const result = await withUserId(5, () =>
			SQMatchRepository.cancelMatch({
				matchId: match.id,
			}),
		);

		expect(result.status).toBe("CANCEL_CONFIRMED");
		expect(result.shouldRefreshCaches).toBe(true);

		const alphaGroup = await fetchGroup(alphaGroupId);
		const bravoGroup = await fetchGroup(bravoGroupId);
		expect(alphaGroup?.status).toBe("INACTIVE");
		expect(bravoGroup?.status).toBe("INACTIVE");

		const skills = await fetchSkills(match.id);
		expect(skills).toHaveLength(1);
		expect(skills[0].season).toBe(-1);
	});

	test("cant cancel after score reported", async () => {
		const alphaGroupId = await createGroup([1, 2, 3, 4]);
		const bravoGroupId = await createGroup([5, 6, 7, 8]);
		const match = await createMatch(alphaGroupId, bravoGroupId);

		await SQMatchRepository.reportMapWinner({
			matchId: match.id,
			winnerId: alphaGroupId,
			reportedByUserId: 1,
			reportedCount: 0,
		});

		const result = await withUserId(5, () =>
			SQMatchRepository.cancelMatch({
				matchId: match.id,
			}),
		);

		expect(result.status).toBe("CANT_CANCEL");
		expect(result.shouldRefreshCaches).toBe(false);
	});

	test("admin cancel locks match without applying SP changes", async () => {
		const alphaGroupId = await createGroup([1, 2, 3, 4]);
		const bravoGroupId = await createGroup([5, 6, 7, 8]);
		const match = await createMatch(alphaGroupId, bravoGroupId);

		const adminUserId = 1;
		const result = await withUserId(adminUserId, () =>
			SQMatchRepository.cancelMatch({
				matchId: match.id,
				isAdminReport: true,
			}),
		);

		expect(result.status).toBe("CANCEL_CONFIRMED");
		expect(result.shouldRefreshCaches).toBe(true);

		const alphaGroup = await fetchGroup(alphaGroupId);
		const bravoGroup = await fetchGroup(bravoGroupId);
		expect(alphaGroup?.status).toBe("INACTIVE");
		expect(bravoGroup?.status).toBe("INACTIVE");

		const skills = await fetchSkills(match.id);
		const realSkills = skills.filter((s) => s.season !== -1);
		expect(realSkills).toHaveLength(0);
		expect(skills).toHaveLength(1);
		expect(skills[0].season).toBe(-1);

		const maps = await fetchMapResults(match.id);
		for (const map of maps) {
			expect(map.winnerGroupId).toBeNull();
		}
	});
});
