import type { Page } from "@playwright/test";
import {
	clockFormatSchema,
	disableBuildAbilitySortingSchema,
	spoilerFreeModeSchema,
} from "~/features/settings/settings-schemas";
import {
	CALENDAR_PAGE,
	SETTINGS_PAGE,
	tournamentBracketsPage,
	tournamentResultsPage,
} from "~/utils/urls";
import {
	expect,
	impersonate,
	isNotVisible,
	navigate,
	seed,
	submit,
	test,
	waitForPOSTResponse,
} from "./helpers/playwright";
import { createFormHelpers } from "./helpers/playwright-form";

test.describe("Settings", () => {
	test("updates 'disableBuildAbilitySorting'", async ({ page }) => {
		await seed(page);
		await impersonate(page);

		await navigate({
			page,
			url: "/builds/luna-blaster",
		});

		const oldContents = await page
			.getByTestId("build-card")
			.first()
			.innerHTML();

		await navigate({
			page,
			url: `${SETTINGS_PAGE}?tab=preferences`,
		});

		const form = createFormHelpers(page, disableBuildAbilitySortingSchema);
		await waitForPOSTResponse(page, () => form.check("newValue"));

		await navigate({
			page,
			url: "/builds/luna-blaster",
		});

		const newContents = await page
			.getByTestId("build-card")
			.first()
			.innerHTML();

		expect(newContents).not.toBe(oldContents);
	});

	test("updates clock format preference", async ({ page }) => {
		await seed(page);
		await impersonate(page);

		await navigate({
			page,
			url: CALENDAR_PAGE,
		});

		const clockTime = page.getByTestId("clock-header-time").first();
		const initialTime = await clockTime.textContent();

		expect(initialTime).toMatch(/AM|PM/);

		await navigate({
			page,
			url: `${SETTINGS_PAGE}?tab=locale`,
		});

		const form = createFormHelpers(page, clockFormatSchema);
		await waitForPOSTResponse(page, () => form.select("newValue", "24h"));

		await navigate({
			page,
			url: CALENDAR_PAGE,
		});

		const newTime = await clockTime.textContent();

		expect(newTime).not.toMatch(/AM|PM/);
		expect(newTime).not.toBe(initialTime);
		expect(newTime).toContain(":");
	});
});

const AVOIDED_MODE_POOL_ERROR =
	"Can't have map pool for a mode that was avoided";

const setModePreference = (
	page: Page,
	mode: string,
	preference: "Avoid" | "Neutral" | "Prefer",
) => {
	const name =
		preference === "Neutral"
			? "Neutral towards the mode"
			: `${preference} the mode`;
	return page
		.getByRole("radiogroup", { name: `Select preference towards ${mode}` })
		.getByRole("radio", { name })
		.click({ force: true });
};

const mapButton = (page: Page, mode: string, stageId: number) =>
	page.getByTestId(`map-pool-${mode}-${stageId}`);

const selectModeTab = (page: Page, mode: string) =>
	page.getByTestId(`map-pool-mode-tab-${mode}`).click();

const SELECTED_MAP_CLASS = /mapButtonGreyedOut/;

// The seeded user already has random map pools, so empty the mode's pool to get
// a known starting state. Only currently selected (greyed, non-banned) stages
// are clickable to deselect.
const clearMapPool = async (page: Page, mode: string) => {
	const picked = page.locator(
		`[data-testid^="map-pool-${mode}-"][class*="mapButtonGreyedOut"]:not([disabled])`,
	);
	for (let count = await picked.count(); count > 0; count--) {
		// the selected-state check icon overlays the button and intercepts clicks
		await picked.first().click({ force: true });
		await expect(picked).toHaveCount(count - 1);
	}
};

test.describe("Match profile map preferences", () => {
	test("retains map selection when toggling a mode prefer -> avoid -> prefer", async ({
		page,
	}) => {
		await seed(page);
		await impersonate(page);
		await navigate({ page, url: SETTINGS_PAGE });

		await setModePreference(page, "SZ", "Prefer");
		await selectModeTab(page, "SZ");
		await clearMapPool(page, "SZ");
		await mapButton(page, "SZ", 1).click();
		await expect(mapButton(page, "SZ", 1)).toHaveClass(SELECTED_MAP_CLASS);

		// avoiding hides the picker, but the selection should be remembered
		await setModePreference(page, "SZ", "Avoid");
		await isNotVisible(mapButton(page, "SZ", 1));

		await setModePreference(page, "SZ", "Prefer");
		await selectModeTab(page, "SZ");
		await expect(mapButton(page, "SZ", 1)).toHaveClass(SELECTED_MAP_CLASS);
	});

	test("can save 'zones only' after a now-avoided mode previously had a map pool", async ({
		page,
	}) => {
		await seed(page);
		await impersonate(page);
		await navigate({ page, url: SETTINGS_PAGE });

		// Save a map pool for both SZ and TC (stage 2 is not banned in TC).
		await setModePreference(page, "SZ", "Prefer");
		await selectModeTab(page, "SZ");
		await clearMapPool(page, "SZ");
		await mapButton(page, "SZ", 1).click();
		await setModePreference(page, "TC", "Prefer");
		await selectModeTab(page, "TC");
		await clearMapPool(page, "TC");
		await mapButton(page, "TC", 2).click();
		await submit(page);

		// Switch to "zones only" by avoiding every mode except SZ, then save.
		await setModePreference(page, "TW", "Avoid");
		await setModePreference(page, "TC", "Avoid");
		await setModePreference(page, "RM", "Avoid");
		await setModePreference(page, "CB", "Avoid");
		await submit(page);

		// Reload so the form loads the persisted preferences. The previously saved
		// TC pool must not resurface as an invalid "pool for an avoided mode".
		await navigate({ page, url: SETTINGS_PAGE });

		await submit(page);
		await isNotVisible(page.getByText(AVOIDED_MODE_POOL_ERROR));
	});
});

const enableSpoilerFreeMode = async (page: Page) => {
	await navigate({ page, url: `${SETTINGS_PAGE}?tab=preferences` });
	const form = createFormHelpers(page, spoilerFreeModeSchema);
	await waitForPOSTResponse(page, () => form.check("newValue"));
};

test.describe("Spoiler-free mode", () => {
	const FINALIZED_TOURNAMENT_ID = 7;

	test("censors bracket and reveals on click", async ({ page }) => {
		await seed(page, "FINALIZED_BRACKET");
		await impersonate(page);
		await enableSpoilerFreeMode(page);

		await navigate({
			page,
			url: tournamentBracketsPage({ tournamentId: FINALIZED_TOURNAMENT_ID }),
		});

		// bracket is censored — "Show results" button visible
		const showResultsButton = page.getByRole("button", {
			name: "Show results",
		});
		await expect(showResultsButton).toBeVisible();

		// later rounds (SF/Finals) show "???" for team names
		await expect(page.getByText("???").first()).toBeVisible();

		// click "Show results" to reveal
		await showResultsButton.click();

		// after reveal, "Hide results" button appears
		await expect(
			page.getByRole("button", { name: "Hide results" }),
		).toBeVisible();

		// "???" no longer present
		await isNotVisible(page.getByText("???"));

		// navigate to results page — sessionStorage reveal carries over
		await navigate({
			page,
			url: tournamentResultsPage(FINALIZED_TOURNAMENT_ID),
		});
		await expect(page.getByTestId("result-team-name").first()).toBeVisible();
	});

	test("results page is censored and can be revealed", async ({ page }) => {
		await seed(page, "FINALIZED_BRACKET");
		await impersonate(page);
		await enableSpoilerFreeMode(page);

		await navigate({
			page,
			url: tournamentResultsPage(FINALIZED_TOURNAMENT_ID),
		});

		// results are censored
		const showResultsButton = page.getByRole("button", {
			name: "Show results",
		});
		await expect(showResultsButton).toBeVisible();
		await isNotVisible(page.getByTestId("result-team-name"));

		// reveal
		await showResultsButton.click();
		await expect(page.getByTestId("result-team-name").first()).toBeVisible();
	});
});
