import type { SerializeFrom } from "@remix-run/node";
import { Form, useLoaderData } from "@remix-run/react";
import clsx from "clsx";
import type { TFunction } from "i18next";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { SendouButton } from "~/components/elements/Button";
import { SendouPopover } from "~/components/elements/Popover";
import {
	SendouTab,
	SendouTabList,
	SendouTabPanel,
	SendouTabs,
} from "~/components/elements/Tabs";
import { Image } from "~/components/Image";
import { CheckmarkIcon } from "~/components/icons/Checkmark";
import { CrossIcon } from "~/components/icons/Cross";
import { PickIcon } from "~/components/icons/Pick";
import { SubmitButton } from "~/components/SubmitButton";
import { useUser } from "~/features/auth/core/user";
import { useChat } from "~/features/chat/chat-hooks";
import { Chat } from "~/features/chat/components/Chat";
import { useTournament } from "~/features/tournament/routes/to.$id";
import { resolveLeagueRoundStartDate } from "~/features/tournament/tournament-utils";
import { useIsMounted } from "~/hooks/useIsMounted";
import { useSearchParamState } from "~/hooks/useSearchParamState";
import type { StageId } from "~/modules/in-game-lists/types";
import { SPLATTERCOLOR_SCREEN_ID } from "~/modules/in-game-lists/weapon-ids";
import type { TournamentMapListMap } from "~/modules/tournament-map-list-generator";
import { nullFilledArray } from "~/utils/arrays";
import { databaseTimestampToDate } from "~/utils/dates";
import type { Unpacked } from "~/utils/types";
import {
	modeImageUrl,
	specialWeaponImageUrl,
	stageImageUrl,
} from "~/utils/urls";
import type { Bracket } from "../core/Bracket";
import * as PickBan from "../core/PickBan";
import type { TournamentDataTeam } from "../core/Tournament.server";
import type { TournamentMatchLoaderData } from "../loaders/to.$id.matches.$mid.server";
import {
	groupNumberToLetters,
	mapCountPlayedInSetWithCertainty,
	matchIsLocked,
	pickInfoText,
	resolveHostingTeam,
	resolveRoomPass,
	tournamentTeamToActiveRosterUserIds,
} from "../tournament-bracket-utils";
import { MatchActions } from "./MatchActions";
import { MatchRosters } from "./MatchRosters";

export type Result = Unpacked<
	SerializeFrom<TournamentMatchLoaderData>["results"]
>;

export function StartedMatch({
	teams,
	currentStageWithMode,
	selectedResultIndex,
	setSelectedResultIndex,
	result,
	type,
}: {
	teams: [TournamentDataTeam, TournamentDataTeam];
	result?: Result;
	currentStageWithMode?: TournamentMapListMap;
	selectedResultIndex?: number;
	// if this is set it means the component is being used in presentation manner
	setSelectedResultIndex?: (index: number) => void;
	type: "EDIT" | "OTHER";
}) {
	const { t } = useTranslation(["tournament"]);
	const isMounted = useIsMounted();
	const user = useUser();
	const tournament = useTournament();
	const data = useLoaderData<TournamentMatchLoaderData>();

	const scoreOne = data.match.opponentOne?.score ?? 0;
	const scoreTwo = data.match.opponentTwo?.score ?? 0;

	const currentPosition = scoreOne + scoreTwo;

	const presentational = Boolean(setSelectedResultIndex);

	const showFullInfos = !presentational && type === "EDIT";

	const isMemberOfTeamParticipating = data.match.players.some(
		(p) => p.id === user?.id,
	);

	const hostingTeamId = resolveHostingTeam(teams).id;
	const poolCode = React.useMemo(() => {
		const match = tournament.brackets
			.flatMap((b) => b.data.match)
			.find((m) => m.id === data.match.id);

		const hasRoundRobin = tournament.brackets.some(
			(b) => b.type === "round_robin",
		);
		const bracketIdx = tournament.brackets.findIndex((b) =>
			b.data.match.some((m) => m.id === data.match.id),
		);
		const bracket = tournament.brackets[bracketIdx] as Bracket | undefined;
		const group = tournament.brackets
			.flatMap((b) => b.data.group)
			.find((group) => group.id === match?.group_id);
		return tournament.resolvePoolCode({
			hostingTeamId,
			groupLetters:
				group && bracket?.type === "round_robin"
					? groupNumberToLetters(group.number)
					: undefined,
			bracketNumber:
				hasRoundRobin && bracket?.type !== "round_robin"
					? bracketIdx + 1
					: undefined,
		});
	}, [tournament, hostingTeamId, data.match.id]);

	// using team ids as the seed to ensure grand finals and bracket reset have the same room pass
	const roomPassSeed = [data.match.opponentOne?.id, data.match.opponentOne?.id]
		.filter((value) => typeof value === "number")
		.sort((a, b) => a - b)
		.join("-");

	const roundInfos = [
		showFullInfos ? (
			<React.Fragment key="hosts">
				{t("tournament:match.hosts", {
					teamName: resolveHostingTeam(teams).name,
				})}
			</React.Fragment>
		) : null,
		showFullInfos ? (
			<React.Fragment key="pass">
				{t("tournament:match.pass")}{" "}
				<span className="text-theme font-bold" data-testid="room-pass">
					{resolveRoomPass(roomPassSeed)}
				</span>
			</React.Fragment>
		) : null,
		showFullInfos ? (
			<span key="pool">
				{t("tournament:match.pool")} {poolCode.prefix}
				<span className="text-theme font-bold">{poolCode.suffix}</span>
			</span>
		) : null,
		<React.Fragment key="score">
			{data.match.roundMaps?.type === "PLAY_ALL"
				? t("tournament:match.score.playAll", {
						scoreOne,
						scoreTwo,
						bestOf: data.match.bestOf,
					})
				: t("tournament:match.score", {
						scoreOne,
						scoreTwo,
						bestOf: data.match.bestOf,
					})}
		</React.Fragment>,
		tournament.ctx.settings.enableNoScreenToggle ? (
			<ScreenBanIcons
				key="screen-ban"
				banned={teams.some((team) => team.noScreen)}
			/>
		) : null,
	];

	return (
		<div className="tournament-bracket__during-match-actions">
			<FancyStageBanner
				stage={currentStageWithMode}
				infos={roundInfos}
				teams={teams}
				matchIsLocked={matchIsLocked({
					matchId: data.match.id,
					scores: [scoreOne, scoreTwo],
					tournament,
				})}
			>
				{currentPosition > 0 &&
					!presentational &&
					type === "EDIT" &&
					(tournament.isOrganizer(user) || isMemberOfTeamParticipating) && (
						<Form method="post">
							<input
								type="hidden"
								name="position"
								value={currentPosition - 1}
							/>
							<div className="tournament-bracket__stage-banner__bottom-bar">
								<SubmitButton
									_action="UNDO_REPORT_SCORE"
									className="tournament-bracket__stage-banner__undo-button"
									testId="undo-score-button"
								>
									{t("tournament:match.action.undoLastScore")}
								</SubmitButton>
							</div>
						</Form>
					)}
				{tournament.isOrganizer(user) &&
					tournament.matchCanBeReopened(data.match.id) &&
					presentational && (
						<Form method="post">
							<div className="tournament-bracket__stage-banner__bottom-bar">
								<SubmitButton
									_action="REOPEN_MATCH"
									className="tournament-bracket__stage-banner__undo-button"
									testId="reopen-match-button"
								>
									{t("tournament:match.action.reopenMatch")}
								</SubmitButton>
							</div>
						</Form>
					)}
			</FancyStageBanner>
			<ModeProgressIndicator
				scores={[scoreOne, scoreTwo]}
				bestOf={data.match.bestOf}
				selectedResultIndex={selectedResultIndex}
				setSelectedResultIndex={setSelectedResultIndex}
			/>
			{type === "EDIT" || presentational ? (
				<StartedMatchTabs
					presentational={presentational}
					scores={[scoreOne, scoreTwo]}
					teams={teams}
					result={result}
				/>
			) : null}
			{result ? (
				<div
					className={clsx("text-center text-xs text-lighter", {
						invisible: !isMounted,
					})}
					data-testid="report-timestamp"
				>
					{isMounted
						? databaseTimestampToDate(result.createdAt).toLocaleString()
						: "t"}
				</div>
			) : null}
		</div>
	);
}

function FancyStageBanner({
	stage,
	infos,
	children,
	teams,
	matchIsLocked,
}: {
	stage?: TournamentMapListMap;
	infos?: (JSX.Element | null)[];
	children?: React.ReactNode;
	teams: [TournamentDataTeam, TournamentDataTeam];
	matchIsLocked: boolean;
}) {
	const data = useLoaderData<TournamentMatchLoaderData>();
	const { t } = useTranslation(["game-misc", "tournament"]);
	const tournament = useTournament();

	const stageNameToBannerImageUrl = (stageId: StageId) => {
		return `${stageImageUrl(stageId)}.png`;
	};

	const banPickingTeam = () => {
		if (
			!data.match.roundMaps ||
			!data.match.opponentOne?.id ||
			!data.match.opponentTwo?.id
		) {
			return null;
		}

		const pickingTeamId = PickBan.turnOf({
			results: data.results,
			maps: data.match.roundMaps,
			teams: [data.match.opponentOne.id, data.match.opponentTwo.id],
			mapList: data.mapList,
		});

		return pickingTeamId ? teams.find((t) => t.id === pickingTeamId) : null;
	};

	const style = {
		"--_tournament-bg-url": stage
			? `url("${stageNameToBannerImageUrl(stage.stageId)}")`
			: undefined,
	};

	const inBanPhase =
		data.match.roundMaps?.pickBan === "BAN_2" &&
		data.mapList &&
		data.mapList.filter((m) => m.bannedByTournamentTeamId).length < 2;

	const waitingForActiveRosterSelectionFor = (() => {
		if (data.results.length > 0) return null;

		const teamOneMissing = !tournamentTeamToActiveRosterUserIds(
			teams[0],
			tournament.minMembersPerTeam,
		);
		const teamTwoMissing = !tournamentTeamToActiveRosterUserIds(
			teams[1],
			tournament.minMembersPerTeam,
		);

		if (teamOneMissing && teamTwoMissing) {
			return "BOTH";
		}

		if (teamOneMissing) {
			return teams[0].name;
		}

		if (teamTwoMissing) {
			return teams[1].name;
		}

		return null;
	})();

	const waitingForLeagueRoundToStart = (() => {
		const date = resolveLeagueRoundStartDate(tournament, data.match.roundId);

		if (!date) return false;

		return date > new Date();
	})();

	return (
		<>
			{inBanPhase ? (
				<div className="tournament-bracket__locked-banner">
					<div className="stack sm items-center">
						<div className="text-lg text-center font-bold">Banning phase</div>
						<div>Waiting for {banPickingTeam()?.name}</div>
					</div>
				</div>
			) : !stage ? (
				<div className="tournament-bracket__locked-banner">
					<div className="stack sm items-center">
						<div className="text-lg text-center font-bold">Counterpick</div>
						<div>Waiting for {banPickingTeam()?.name}</div>
						{children}
					</div>
				</div>
			) : matchIsLocked ? (
				<div className="tournament-bracket__locked-banner">
					<div className="stack sm items-center">
						<div className="text-lg text-center font-bold">
							Match locked to be casted
						</div>
						<div>Please wait for staff to unlock</div>
					</div>
				</div>
			) : waitingForLeagueRoundToStart ? (
				<div className="tournament-bracket__locked-banner">
					<div className="stack sm items-center">
						<div className="text-lg text-center font-bold">
							Waiting for league round to start
						</div>
						<div>
							Round playable from{" "}
							{resolveLeagueRoundStartDate(
								tournament,
								data.match.roundId,
							)!.toLocaleDateString()}{" "}
							onwards
						</div>
					</div>
				</div>
			) : waitingForActiveRosterSelectionFor ? (
				<div className="tournament-bracket__locked-banner">
					<div className="stack sm items-center">
						<div
							className="text-lg text-center font-bold"
							data-testid="active-roster-needed-text"
						>
							Active rosters need to be selected
						</div>
						<div>
							Waiting on{" "}
							{waitingForActiveRosterSelectionFor === "BOTH"
								? "both teams"
								: waitingForActiveRosterSelectionFor}
						</div>
					</div>
				</div>
			) : (
				<div
					className={clsx("tournament-bracket__stage-banner", {
						rounded: !infos,
					})}
					style={style}
					data-testid="stage-banner"
				>
					<div className="tournament-bracket__stage-banner__top-bar">
						<h4 className="tournament-bracket__stage-banner__top-bar__header">
							<Image
								className="tournament-bracket__stage-banner__top-bar__mode-image"
								path={modeImageUrl(stage.mode)}
								alt=""
								width={24}
							/>
							<span className="tournament-bracket__stage-banner__top-bar__map-text-small">
								{t(`game-misc:MODE_SHORT_${stage.mode}`)}{" "}
								{t(`game-misc:STAGE_${stage.stageId}`)}
							</span>
							<span className="tournament-bracket__stage-banner__top-bar__map-text-big">
								{t(`game-misc:MODE_LONG_${stage.mode}`)} on{" "}
								{t(`game-misc:STAGE_${stage.stageId}`)}
							</span>
						</h4>
						<h4>
							{pickInfoText({
								t: t as unknown as TFunction<["tournament"]>,
								teams,
								map: stage,
							})}
						</h4>
					</div>
					{children}
				</div>
			)}
			{infos && (
				<div className="tournament-bracket__infos">
					{infos.filter(Boolean).map((info, i) => (
						<div key={i}>{info}</div>
					))}
				</div>
			)}
		</>
	);
}

function ModeProgressIndicator({
	scores,
	bestOf,
	selectedResultIndex,
	setSelectedResultIndex,
}: {
	scores: [number, number];
	bestOf: number;
	selectedResultIndex?: number;
	setSelectedResultIndex?: (index: number) => void;
}) {
	const tournament = useTournament();
	const data = useLoaderData<TournamentMatchLoaderData>();
	const { t } = useTranslation(["game-misc"]);

	const maxIndexThatWillBePlayedForSure =
		data.match.roundMaps?.type === "PLAY_ALL"
			? bestOf - 1
			: mapCountPlayedInSetWithCertainty({ bestOf, scores }) - 1;

	const indexWithBansConsider = (realIdx: number) => {
		let result = 0;

		for (const [idx, map] of (data.mapList ?? []).entries()) {
			if (idx === realIdx) {
				break;
			}

			if (map.bannedByTournamentTeamId) {
				continue;
			}

			result++;
		}

		return result;
	};

	// TODO: this should be button when we click on it
	return (
		<div className="tournament-bracket__mode-progress">
			<div className="tournament-bracket__mode-progress__inner">
				{nullFilledArray(
					Math.max(data.mapList?.length ?? 0, data.match.roundMaps?.count ?? 0),
				).map((_, i) => {
					const map = data.mapList?.[i];

					const adjustedI = indexWithBansConsider(i);

					if (
						data.matchIsOver &&
						!data.results[adjustedI] &&
						!map?.bannedByTournamentTeamId
					) {
						return null;
					}

					if (!map?.mode) {
						return (
							<div key={i} className="tournament-bracket__mode-progress__image">
								<PickIcon />
							</div>
						);
					}

					if (map.bannedByTournamentTeamId) {
						const bannerTeamName = tournament.ctx.teams.find(
							(t) => t.id === map.bannedByTournamentTeamId,
						)?.name;

						return (
							<SendouPopover
								key={i}
								trigger={
									<SendouButton
										variant="minimal"
										size="small"
										className="tournament-bracket__mode-progress__image__banned__popover-trigger"
									>
										<Image
											containerClassName="tournament-bracket__mode-progress__image tournament-bracket__mode-progress__image__banned"
											path={modeImageUrl(map.mode)}
											height={20}
											width={20}
											alt={t(`game-misc:MODE_LONG_${map.mode}`)}
										/>
									</SendouButton>
								}
							>
								<div className="text-center">
									{t(`game-misc:MODE_SHORT_${map.mode}`)}{" "}
									{t(`game-misc:STAGE_${map.stageId}`)}
								</div>
								<div className="text-xs text-lighter">
									Banned by {bannerTeamName}
								</div>
							</SendouPopover>
						);
					}

					return (
						<Image
							containerClassName={clsx(
								"tournament-bracket__mode-progress__image",
								{
									"tournament-bracket__mode-progress__image__notable":
										adjustedI <= maxIndexThatWillBePlayedForSure,
									"tournament-bracket__mode-progress__image__team-one-win":
										data.results[adjustedI] &&
										data.results[adjustedI].winnerTeamId ===
											data.match.opponentOne?.id,
									"tournament-bracket__mode-progress__image__team-two-win":
										data.results[adjustedI] &&
										data.results[adjustedI].winnerTeamId ===
											data.match.opponentTwo?.id,
									"tournament-bracket__mode-progress__image__selected":
										adjustedI === selectedResultIndex,
									"cursor-pointer": Boolean(setSelectedResultIndex),
								},
							)}
							key={i}
							path={modeImageUrl(map.mode)}
							height={20}
							width={20}
							alt={t(`game-misc:MODE_LONG_${map.mode}`)}
							title={t(`game-misc:MODE_LONG_${map.mode}`)}
							onClick={() => setSelectedResultIndex?.(adjustedI)}
							testId={`mode-progress-${map.mode}`}
						/>
					);
				})}
			</div>
		</div>
	);
}

function StartedMatchTabs({
	presentational,
	scores,
	teams,
	result,
}: {
	presentational?: boolean;
	scores: [number, number];
	teams: [TournamentDataTeam, TournamentDataTeam];
	result?: Result;
}) {
	const user = useUser();
	const tournament = useTournament();
	const data = useLoaderData<TournamentMatchLoaderData>();
	const [_unseenMessages, setUnseenMessages] = React.useState(0);
	const [chatVisible, setChatVisible] = React.useState(false);
	const [selectedTabKey, setSelectedTabKey] = useSearchParamState({
		defaultValue: "rosters",
		name: "tab",
		revive: (value) =>
			["chat", "rosters", "actions"].includes(value) ? value : null,
	});

	// TODO: resolve this on server (notice it is copy-pasted now)
	const chatUsers = React.useMemo(() => {
		return Object.fromEntries(
			[
				...data.match.players.map((p) => ({ ...p, title: undefined })),
				...(tournament.ctx.organization?.members ?? []).map((m) => ({
					...m,
					title: m.role === "STREAMER" ? "Stream" : "TO",
				})),
				...tournament.ctx.staff.map((s) => ({
					...s,
					title: s.role === "STREAMER" ? "Stream" : "TO",
				})),
				{
					...tournament.ctx.author,
					title: "TO",
				},
			].map((p) => [p.id, p]),
		);
	}, [data, tournament]);

	const showChat = (() => {
		if (!data.match.chatCode) return false;
		if (tournament.ctx.isFinalized && !tournament.isOrganizer(user)) {
			return false;
		}
		const oneMonthAgo = new Date();
		oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
		if (
			tournament.ctx.startTime < oneMonthAgo &&
			!tournament.isLeagueDivision
		) {
			return false;
		}

		return (
			data.match.players.some((p) => p.id === user?.id) ||
			tournament.isOrganizerOrStreamer(user)
		);
	})();

	const rooms = React.useMemo(() => {
		return showChat && data.match.chatCode
			? [
					{
						code: data.match.chatCode,
						label: "Match",
					},
				]
			: [];
	}, [showChat, data.match.chatCode]);

	const onNewMessage = React.useCallback(() => {
		setUnseenMessages((msg) => msg + 1);
	}, []);

	const chat = useChat({ rooms, onNewMessage });

	const onChatMount = React.useCallback(() => {
		setChatVisible(true);
	}, []);

	const onChatUnmount = React.useCallback(() => {
		setChatVisible(false);
		setUnseenMessages(0);
	}, []);

	const unseenMessages = chatVisible ? 0 : _unseenMessages;

	const currentPosition = scores[0] + scores[1];

	const matchActionsKey = () =>
		[
			data.match.id,
			tournamentTeamToActiveRosterUserIds(
				teams[0],
				tournament.minMembersPerTeam,
			),
			tournamentTeamToActiveRosterUserIds(
				teams[1],
				tournament.minMembersPerTeam,
			),
			result?.participants
				.map((p) => `${p.userId}-${p.tournamentTeamId}`)
				.join(","),
			result?.opponentOnePoints,
			result?.opponentTwoPoints,
			data.results.length,
		].join("-");

	return (
		<ActionSectionWrapper>
			<SendouTabs
				selectedKey={selectedTabKey}
				onSelectionChange={(key) => setSelectedTabKey(String(key))}
			>
				<SendouTabList>
					{showChat && (
						<SendouTab id="chat" number={unseenMessages} data-testid="chat-tab">
							Chat
						</SendouTab>
					)}
					<SendouTab id="rosters">Rosters</SendouTab>
					<SendouTab id="actions" data-testid="actions-tab">
						{presentational ? "Score" : "Actions"}
					</SendouTab>
				</SendouTabList>

				<SendouTabPanel id="chat">
					<Chat
						rooms={rooms}
						users={chatUsers}
						className="tournament__chat-container"
						messagesContainerClassName="tournament__chat-messages-container pt-0"
						chat={chat}
						onMount={onChatMount}
						onUnmount={onChatUnmount}
						missingUserName="???"
					/>
				</SendouTabPanel>

				<SendouTabPanel id="rosters">
					<MatchRosters teams={[teams[0].id, teams[1].id]} />
				</SendouTabPanel>

				<SendouTabPanel
					id="actions"
					shouldForceMount
					className={clsx({
						hidden: selectedTabKey !== "actions",
					})}
				>
					<MatchActions
						// Without the key prop when switching to another match the winnerId is remembered
						// which causes "No winning team matching the id" error.
						// In addition we want the active roster changing either by the user or by another user
						// to reset the state inside. We also want to clear the inputs when a result is submitted
						key={matchActionsKey()}
						scores={scores}
						teams={teams}
						position={currentPosition}
						result={result}
						presentational={
							!tournament.canReportScore({ matchId: data.match.id, user })
						}
					/>
				</SendouTabPanel>
			</SendouTabs>
		</ActionSectionWrapper>
	);
}

function ActionSectionWrapper({
	children,
	icon,
	...rest
}: {
	children: React.ReactNode;
	icon?: "warning" | "info" | "success" | "error";
	"justify-center"?: boolean;
}) {
	// todo: flex-dir: column on mobile
	const style = icon
		? {
				"--action-section-icon-color": `var(--theme-${icon})`,
			}
		: undefined;
	return (
		<section className="tournament__action-section" style={style}>
			<div
				className={clsx("tournament__action-section__content", {
					"justify-center": rest["justify-center"],
				})}
			>
				{children}
			</div>
		</section>
	);
}

function ScreenBanIcons({ banned }: { banned: boolean }) {
	const { t } = useTranslation(["weapons"]);

	return (
		<div
			className={clsx("tournament-bracket__no-screen", {
				"tournament-bracket__no-screen__banned": banned,
			})}
			data-testid={`screen-${banned ? "banned" : "allowed"}`}
		>
			{banned ? <CrossIcon /> : <CheckmarkIcon />}
			<Image
				path={specialWeaponImageUrl(SPLATTERCOLOR_SCREEN_ID)}
				width={24}
				height={24}
				alt={t(`weapons:SPECIAL_${SPLATTERCOLOR_SCREEN_ID}`)}
			/>
		</div>
	);
}
