import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";

vi.mock("~/features/chat/ChatSystemMessage.server", () => ({
	send: vi.fn(),
	removeRoom: vi.fn(),
	setMetadata: vi.fn(),
}));

import { db } from "~/db/sql";
import type { UserMapModePreferences } from "~/db/tables";
import { BANNED_MAPS } from "~/features/match-profile/banned-maps";
import { stageIds } from "~/modules/in-game-lists/stage-ids";
import invariant from "~/utils/invariant";
import { dbInsertUsers, dbReset, wrappedAction } from "~/utils/Test";
import { refreshSendouQInstance } from "../core/SendouQ.server";
import type { lookingSchema } from "../q-schemas.server";
import { action as rawLookingAction } from "./q.looking";

const createGroup = async (userIds: number[]) => {
	const group = await db
		.insertInto("Group")
		.values({
			inviteCode: "1234",
			status: "ACTIVE",
		})
		.returning("id")
		.executeTakeFirstOrThrow();

	await db
		.insertInto("GroupMember")
		.values(
			userIds.map((userId, i) => ({
				groupId: group.id,
				userId,
				role: i === 0 ? "OWNER" : "REGULAR",
			})),
		)
		.execute();
};

const SZ_ONLY_PREFERENCE: UserMapModePreferences["modes"] = [
	{ mode: "SZ", preference: "PREFER" },
	{ mode: "TC", preference: "AVOID" },
	{ mode: "RM", preference: "AVOID" },
	{ mode: "CB", preference: "AVOID" },
];

const prepareGroups = async () => {
	await dbInsertUsers(8);
	await createGroup([1, 2, 3, 4]);
	await createGroup([5, 6, 7, 8]);
	await db
		.insertInto("GroupLike")
		.values({ likerGroupId: 2, targetGroupId: 1 })
		.execute();

	await insertMapModePreferences(1, {
		modes: SZ_ONLY_PREFERENCE,
		pool: [{ mode: "SZ", stages: [...stageIds].slice(0, 7) }],
	});

	await insertMapModePreferences(5, {
		modes: SZ_ONLY_PREFERENCE,
		pool: [
			{ mode: "SZ", stages: [...stageIds].slice(0, 20).reverse().slice(0, 7) },
		],
	});
};

const insertMapModePreferences = (
	userId: number,
	preferences: UserMapModePreferences,
) => {
	return db
		.updateTable("User")
		.set({
			mapModePreferences: JSON.stringify(preferences),
		})
		.where("User.id", "=", userId)
		.execute();
};

const lookingAction = wrappedAction<typeof lookingSchema>({
	action: rawLookingAction,
});

const createMatch = () =>
	lookingAction(
		{
			_action: "MATCH_UP",
			targetGroupId: 2,
		},
		{ user: "admin" },
	);

const findMatch = () =>
	db
		.selectFrom("GroupMatch")
		.selectAll()
		.where("id", "=", 1)
		.executeTakeFirstOrThrow();

describe("SendouQ match creation", () => {
	beforeEach(async () => {
		await prepareGroups();
		await refreshSendouQInstance();
	});

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

	test("adds pools to memento", async () => {
		await createMatch();

		const match = await findMatch();
		const pools = match.memento?.pools;

		invariant(pools, "pools missing");

		expect(pools.length).toBe(2);
		expect(pools.some((p) => p.pool[0].stages.includes(1))).toBe(true);
		expect(pools.some((p) => p.pool[0].stages.includes(19))).toBe(true);
	});

	test("doesn't add pool where mode is avoided", async () => {
		await insertMapModePreferences(1, {
			modes: [
				{ mode: "SZ", preference: "AVOID" },
				{ mode: "TC", preference: "PREFER" },
			],
			pool: [
				{
					mode: "TC",
					stages: [...stageIds]
						.filter((stageId) => !BANNED_MAPS.TC.includes(stageId))
						.slice(0, 7),
				},
			],
		});

		await createMatch();

		const match = await findMatch();
		const pools = match.memento?.pools;

		invariant(pools, "pools missing");

		expect(pools.length).toBe(2);
		expect(
			pools.find((p) => p.userId === 1)!.pool.every((p) => p.mode !== "SZ"),
		).toBe(true);
	});

	test("adds mode preferences to memento", async () => {
		await createMatch();

		const match = await findMatch();

		const modePreferences = match.memento?.modePreferences;

		expect(modePreferences?.SZ?.length).toBe(2);
	});

	test("adds mode preferences to memento including neutral", async () => {
		await insertMapModePreferences(2, {
			modes: [{ mode: "TC", preference: "PREFER" }],
			pool: [],
		});

		await createMatch();

		const match = await findMatch();

		const modePreferences = match.memento?.modePreferences;

		expect(modePreferences?.SZ?.length).toBe(3);
		expect(modePreferences?.SZ?.some((p) => !p.preference)).toBe(true);
	});
});
