import type { Page } from "@playwright/test";
import { ORG_ADMIN_TEST_ID } from "~/db/seed/constants";
import { ADMIN_ID } from "~/features/admin/admin-constants";
import { tournamentTeamPage } from "~/utils/urls";
import {
	expect,
	impersonate,
	navigate,
	seed,
	test,
} from "./helpers/playwright";

const ITZ_TOURNAMENT_ID = 2;
const ITZ_TEAM_ID = 101;
const USER_NOT_ON_ITZ_TEAM = 100;

async function generateReadToken(page: Page): Promise<string> {
	await navigate({ page, url: "/api" });

	await page.locator("form").first().getByRole("button").click();
	await page.waitForURL("/api");

	await page
		.getByRole("button", { name: /reveal/i })
		.first()
		.click();
	const token = await page.locator("input[readonly]").inputValue();

	return token;
}

async function generateWriteToken(page: Page): Promise<string> {
	await navigate({ page, url: "/api" });

	// Click the second form (write token)
	await page.locator("form").nth(1).getByRole("button").click();
	await page.waitForURL("/api");

	// Reveal and get the write token
	// After generating only the write token, there's just one reveal button (for write)
	await page.getByRole("button", { name: /reveal/i }).click();
	const token = await page.locator("input[readonly]").inputValue();

	return token;
}

test.describe("Public API", () => {
	test("OPTIONS preflight request returns 204 with CORS headers", async ({
		page,
	}) => {
		await seed(page);

		const response = await page.request.fetch("/api/tournament/1", {
			method: "OPTIONS",
		});

		expect(response.status()).toBe(204);
		expect(response.headers()["access-control-allow-origin"]).toBe("*");
		expect(response.headers()["access-control-allow-methods"]).toContain("GET");
		expect(response.headers()["access-control-allow-headers"]).toContain(
			"Authorization",
		);
	});

	test("GET request includes CORS headers in response", async ({ page }) => {
		await seed(page);

		const response = await page.request.fetch("/api/tournament/1");

		expect(response.headers()["access-control-allow-origin"]).toBe("*");
	});

	test("GET user IDs endpoint works without authentication", async ({
		page,
	}) => {
		await seed(page);

		const response = await page.request.fetch(`/api/user/${ADMIN_ID}/ids`);

		expect(response.status()).toBe(200);
		const data = await response.json();
		expect(data.id).toBe(ADMIN_ID);
		expect(data.discordId).toBeTruthy();
	});

	test("creates read API token and calls public endpoint", async ({ page }) => {
		await seed(page);
		await impersonate(page);

		await navigate({ page, url: "/api" });

		await page.locator("form").first().getByRole("button").click();
		await page.waitForURL("/api");

		await page
			.getByRole("button", { name: /reveal/i })
			.first()
			.click();

		const token = await page.locator("input[readonly]").inputValue();
		expect(token).toBeTruthy();
		expect(token.length).toBe(20);

		const response = await page.request.fetch(`/api/user/${ADMIN_ID}`, {
			headers: {
				Authorization: `Bearer ${token}`,
			},
		});

		expect(response.status()).toBe(200);
		const data = await response.json();
		expect(data.id).toBe(ADMIN_ID);
		expect(data.name).toBe("Sendou");
	});

	test("returns active SendouQ match for user", async ({ page }) => {
		await seed(page, "IN_SQ_MATCH");
		await impersonate(page);

		const token = await generateReadToken(page);

		const response = await page.request.fetch(
			`/api/user/${ADMIN_ID}/active-match`,
			{
				headers: {
					Authorization: `Bearer ${token}`,
				},
			},
		);

		expect(response.status()).toBe(200);
		const data = await response.json();
		expect(data.matchId).toEqual(expect.any(Number));
		expect(data.lobby).toBe("sendouq");
		expect(data.tournamentId).toBeNull();
		expect(data.bracketIdx).toBeNull();
	});
});

test.describe("Public API - Write endpoints", () => {
	test("adds member to tournament team via API", async ({ page }) => {
		await seed(page);
		await impersonate(page, ADMIN_ID);

		const token = await generateWriteToken(page);

		const response = await page.request.fetch(
			`/api/tournament/${ITZ_TOURNAMENT_ID}/teams/${ITZ_TEAM_ID}/add-member`,
			{
				method: "POST",
				headers: {
					Authorization: `Bearer ${token}`,
					"Content-Type": "application/json",
				},
				data: { userId: USER_NOT_ON_ITZ_TEAM },
			},
		);

		expect(response.status()).toBe(200);

		// Verify in UI that member was added
		await navigate({
			page,
			url: tournamentTeamPage({
				tournamentId: ITZ_TOURNAMENT_ID,
				tournamentTeamId: ITZ_TEAM_ID,
			}),
		});

		// User 100 should be visible on the team page
		await expect(page.getByTestId("team-member-name")).toHaveCount(5);
	});

	test("removes member from tournament team via API", async ({ page }) => {
		await seed(page);
		await impersonate(page, ADMIN_ID);

		const token = await generateWriteToken(page);

		// First add the member
		await page.request.fetch(
			`/api/tournament/${ITZ_TOURNAMENT_ID}/teams/${ITZ_TEAM_ID}/add-member`,
			{
				method: "POST",
				headers: {
					Authorization: `Bearer ${token}`,
					"Content-Type": "application/json",
				},
				data: { userId: USER_NOT_ON_ITZ_TEAM },
			},
		);

		// Verify member was added
		await navigate({
			page,
			url: tournamentTeamPage({
				tournamentId: ITZ_TOURNAMENT_ID,
				tournamentTeamId: ITZ_TEAM_ID,
			}),
		});
		await expect(page.getByTestId("team-member-name")).toHaveCount(5);

		// Remove the member via API
		const response = await page.request.fetch(
			`/api/tournament/${ITZ_TOURNAMENT_ID}/teams/${ITZ_TEAM_ID}/remove-member`,
			{
				method: "POST",
				headers: {
					Authorization: `Bearer ${token}`,
					"Content-Type": "application/json",
				},
				data: { userId: USER_NOT_ON_ITZ_TEAM },
			},
		);

		expect(response.status()).toBe(200);

		// Verify in UI that member was removed
		await page.reload();
		await expect(page.getByTestId("team-member-name")).toHaveCount(4);
	});

	test("returns 401 for invalid token", async ({ page }) => {
		await seed(page);

		const response = await page.request.fetch(
			`/api/tournament/${ITZ_TOURNAMENT_ID}/teams/${ITZ_TEAM_ID}/add-member`,
			{
				method: "POST",
				headers: {
					Authorization: "Bearer invalid-token-12345",
					"Content-Type": "application/json",
				},
				data: { userId: USER_NOT_ON_ITZ_TEAM },
			},
		);

		expect(response.status()).toBe(401);
		const data = await response.json();
		expect(data.error).toBe("Invalid token");
	});

	test("returns 403 when using read token for write endpoint", async ({
		page,
	}) => {
		await seed(page);
		await impersonate(page, ADMIN_ID);

		await navigate({ page, url: "/api" });

		// Click the first form (read token)
		await page.locator("form").first().getByRole("button").click();
		await page.waitForURL("/api");

		// Reveal and get the read token
		await page
			.getByRole("button", { name: /reveal/i })
			.first()
			.click();
		const readToken = await page
			.locator("input[readonly]")
			.first()
			.inputValue();

		const response = await page.request.fetch(
			`/api/tournament/${ITZ_TOURNAMENT_ID}/teams/${ITZ_TEAM_ID}/add-member`,
			{
				method: "POST",
				headers: {
					Authorization: `Bearer ${readToken}`,
					"Content-Type": "application/json",
				},
				data: { userId: USER_NOT_ON_ITZ_TEAM },
			},
		);

		expect(response.status()).toBe(403);
		const data = await response.json();
		expect(data.error).toBe("Write token required");
	});

	test("updates tournament seeds via API", async ({ page }) => {
		await seed(page);
		await impersonate(page, ADMIN_ID);

		const token = await generateWriteToken(page);

		const teamsResponse = await page.request.fetch(
			`/api/tournament/${ITZ_TOURNAMENT_ID}/teams`,
			{
				headers: { Authorization: `Bearer ${token}` },
			},
		);
		expect(teamsResponse.status()).toBe(200);
		const teams = await teamsResponse.json();
		const tournamentTeamIds = teams.map((t: { id: number }) => t.id);
		const reversedSeeds = [...tournamentTeamIds].reverse();

		const response = await page.request.fetch(
			`/api/tournament/${ITZ_TOURNAMENT_ID}/seeds`,
			{
				method: "POST",
				headers: {
					Authorization: `Bearer ${token}`,
					"Content-Type": "application/json",
				},
				data: { tournamentTeamIds: reversedSeeds },
			},
		);

		expect(response.status()).toBe(200);

		const updatedTeamsResponse = await page.request.fetch(
			`/api/tournament/${ITZ_TOURNAMENT_ID}/teams`,
			{
				headers: { Authorization: `Bearer ${token}` },
			},
		);
		const updatedTeams = await updatedTeamsResponse.json();

		for (let i = 0; i < reversedSeeds.length; i++) {
			const team = updatedTeams.find(
				(t: { id: number }) => t.id === reversedSeeds[i],
			);
			expect(team.seed).toBe(i + 1);
		}
	});

	test("updates tournament starting brackets via API", async ({ page }) => {
		await seed(page);
		await impersonate(page, ADMIN_ID);

		const token = await generateWriteToken(page);

		const teamsResponse = await page.request.fetch(
			`/api/tournament/${ITZ_TOURNAMENT_ID}/teams`,
			{
				headers: { Authorization: `Bearer ${token}` },
			},
		);
		expect(teamsResponse.status()).toBe(200);
		const teams = await teamsResponse.json();
		const firstTeamId = teams[0].id;

		const response = await page.request.fetch(
			`/api/tournament/${ITZ_TOURNAMENT_ID}/starting-brackets`,
			{
				method: "POST",
				headers: {
					Authorization: `Bearer ${token}`,
					"Content-Type": "application/json",
				},
				data: {
					startingBrackets: [
						{ tournamentTeamId: firstTeamId, startingBracketIdx: 0 },
					],
				},
			},
		);

		expect(response.status()).toBe(200);
	});

	test("updates member IGN via API", async ({ page }) => {
		await seed(page);
		await impersonate(page, ADMIN_ID);

		const token = await generateWriteToken(page);

		const response = await page.request.fetch(
			`/api/tournament/${ITZ_TOURNAMENT_ID}/teams/${ITZ_TEAM_ID}/update-member-ign`,
			{
				method: "POST",
				headers: {
					Authorization: `Bearer ${token}`,
					"Content-Type": "application/json",
				},
				data: { userId: ADMIN_ID, inGameName: "NewName#9999" },
			},
		);

		expect(response.status()).toBe(200);
	});

	test("returns 400 when user is not the organizer of this tournament", async ({
		page,
	}) => {
		await seed(page);
		await impersonate(page, ORG_ADMIN_TEST_ID);

		const token = await generateWriteToken(page);

		const response = await page.request.fetch(
			`/api/tournament/${ITZ_TOURNAMENT_ID}/teams/${ITZ_TEAM_ID}/add-member`,
			{
				method: "POST",
				headers: {
					Authorization: `Bearer ${token}`,
					"Content-Type": "application/json",
				},
				data: { userId: USER_NOT_ON_ITZ_TEAM },
			},
		);

		expect(response.status()).toBe(400);
		const data = await response.json();
		expect(data.error).toBe("Unauthorized");
	});
});
