import clsx from "clsx";
import { Check, SquarePen, X } from "lucide-react";
import * as React from "react";
import { Link, useFetcher } from "react-router";
import invariant from "~/utils/invariant";
import { SendouButton } from "../../../../components/elements/Button";
import { logger } from "../../../../utils/logger";
import {
	tournamentBracketsPage,
	tournamentTeamPage,
} from "../../../../utils/urls";
import { useUser } from "../../../auth/core/user";
import { TOURNAMENT } from "../../../tournament/tournament-constants";
import type { Bracket, Standing } from "../../core/Bracket";
import * as Swiss from "../../core/engine/swiss/team-status";
import * as Progression from "../../core/Progression";
import styles from "./bracket.module.css";

export function PlacementsTable({
	groupId,
	bracket,
	allMatchesFinished,
}: {
	groupId: number;
	bracket: Bracket;
	allMatchesFinished: boolean;
}) {
	const user = useUser();

	const _standings = bracket
		.currentStandings(true)
		.filter((s) => s.groupId === groupId);

	const missingTeams = bracket.data.match.reduce((acc, cur) => {
		if (cur.group_id !== groupId) return acc;

		if (
			cur.opponent1?.id &&
			!_standings.some((s) => s.team.id === cur.opponent1!.id) &&
			!acc.includes(cur.opponent1.id)
		) {
			acc.push(cur.opponent1.id);
		}

		if (
			cur.opponent2?.id &&
			!_standings.some((s) => s.team.id === cur.opponent2!.id) &&
			!acc.includes(cur.opponent2.id)
		) {
			acc.push(cur.opponent2.id);
		}

		return acc;
	}, [] as number[]);

	const standings = _standings
		.concat(
			missingTeams.map((id) => ({
				team: bracket.tournament.teamById(id)!,
				stats: {
					mapLosses: 0,
					mapWins: 0,
					koCount: 0,
					setLosses: 0,
					setWins: 0,
					winsAgainstTied: 0,
					lossesAgainstTied: 0,
				},
				placement: Math.max(..._standings.map((s) => s.placement)) + 1,
				groupId,
			})),
		)
		.sort((a, b) => {
			if (a.placement === b.placement && a.team.seed && b.team.seed) {
				return a.team.seed - b.team.seed;
			}

			return a.placement - b.placement;
		});

	const destinationBracket = (standing: Standing, placement: number) => {
		if (bracket.type === "swiss" && bracket.settings?.advanceThreshold) {
			const stats = standing.stats;
			invariant(stats);

			return Swiss.calculateTeamStatus({
				advanceThreshold: bracket.settings.advanceThreshold,
				losses: stats.setLosses,
				wins: stats.setWins,
				roundCount:
					bracket.settings.roundCount ?? TOURNAMENT.SWISS_DEFAULT_ROUND_COUNT,
			}) === "advanced"
				? bracket.tournament.brackets.find((otherBracket) =>
						otherBracket.sources?.some(
							(source) => source.bracketIdx === bracket.idx,
						),
					)
				: undefined;
		}

		return bracket.tournament.brackets.find(
			(b) =>
				b.idx ===
				Progression.destinationByPlacement({
					sourceBracketIdx: bracket.idx,
					placement,
					progression: bracket.tournament.ctx.settings.bracketProgression,
				}),
		);
	};

	const possibleDestinationBrackets = Progression.destinationsFromBracketIdx(
		bracket.idx,
		bracket.tournament.ctx.settings.bracketProgression,
	).map((idx) => bracket.tournament.bracketByIdx(idx)!);
	const canEditDestination = (() => {
		if (possibleDestinationBrackets.length === 0) return false;

		const allDestinationsPreview = possibleDestinationBrackets.every(
			(b) => b.preview,
		);

		return (
			bracket.tournament.isOrganizer(user) &&
			allDestinationsPreview &&
			allMatchesFinished
		);
	})();

	if (bracket.settings?.hasAbDivisions) {
		const aStandings = standings.filter((s) => s.team.abDivision === 0);
		const bStandings = standings.filter((s) => s.team.abDivision === 1);

		if (aStandings.length === 0 && bStandings.length === 0) {
			return null;
		}

		return (
			<div className="stack lg">
				{aStandings.length > 0 ? (
					<StandingsTable
						bracket={bracket}
						standings={aStandings}
						destinationBracket={destinationBracket}
						possibleDestinationBrackets={possibleDestinationBrackets}
						canEditDestination={canEditDestination}
						allMatchesFinished={allMatchesFinished}
					/>
				) : null}
				{bStandings.length > 0 ? (
					<StandingsTable
						bracket={bracket}
						standings={bStandings}
						destinationBracket={destinationBracket}
						possibleDestinationBrackets={possibleDestinationBrackets}
						canEditDestination={canEditDestination}
						allMatchesFinished={allMatchesFinished}
					/>
				) : null}
			</div>
		);
	}

	if (standings.length === 0) {
		return null;
	}

	return (
		<StandingsTable
			bracket={bracket}
			standings={standings}
			destinationBracket={destinationBracket}
			possibleDestinationBrackets={possibleDestinationBrackets}
			canEditDestination={canEditDestination}
			allMatchesFinished={allMatchesFinished}
		/>
	);
}

function StandingsTable({
	bracket,
	standings,
	destinationBracket,
	possibleDestinationBrackets,
	canEditDestination,
	allMatchesFinished,
}: {
	bracket: Bracket;
	standings: Standing[];
	destinationBracket: (
		standing: Standing,
		placement: number,
	) => Bracket | undefined;
	possibleDestinationBrackets: Bracket[];
	canEditDestination: boolean;
	allMatchesFinished: boolean;
}) {
	let qualifiedRowRendered = false;
	let eliminatedRowRendered = false;

	return (
		<table
			className={styles.rrPlacementsTable}
			cellSpacing={0}
			data-testid="rr-standings-table"
		>
			<thead>
				<tr>
					<th>Team</th>
					<th>
						<abbr title="Set wins and losses">W/L</abbr>
					</th>
					{bracket.type === "round_robin" ? (
						<th>
							<abbr title="Wins against tied opponents">TB</abbr>
						</th>
					) : null}
					{bracket.type === "swiss" ? (
						<th>
							<abbr title="Losses against tied opponents">TB</abbr>
						</th>
					) : null}
					{bracket.type === "swiss" ? (
						<th>
							<abbr title="Opponents' set win percentage average">OW%</abbr>
						</th>
					) : null}
					<th>
						<abbr title="Map wins and losses">W/L (M)</abbr>
					</th>
					{bracket.type === "swiss" ? (
						<th>
							<abbr title="Opponents' map win percentage average">OW% (M)</abbr>
						</th>
					) : null}
					{bracket.type === "round_robin" ? (
						<th>
							<abbr title="Number of maps knocked out">KOs</abbr>
						</th>
					) : null}
					<th>Seed</th>
					<th />
					{canEditDestination ? <th /> : null}
				</tr>
			</thead>
			<tbody>
				{standings.map((s, i) => {
					const stats = s.stats!;
					if (!stats) {
						logger.error("No stats for team", s.team);
						return null;
					}

					const team = bracket.tournament.teamById(s.team.id);

					const dest = destinationBracket(s, i + 1);

					const overridenDestination =
						bracket.tournament.ctx.bracketProgressionOverrides.find(
							(override) =>
								override.sourceBracketIdx === bracket.idx &&
								override.tournamentTeamId === s.team.id,
						);
					const overridenDestinationBracket = overridenDestination
						? bracket.tournament.bracketByIdx(
								overridenDestination.destinationBracketIdx,
							)
						: undefined;

					const key = () => {
						if (overridenDestinationBracket === null) {
							return `${s.team.id}-null`;
						}

						return `${s.team.id}-${overridenDestinationBracket?.idx}`;
					};

					const renderQualifiedRow =
						!qualifiedRowRendered &&
						bracket.settings?.advanceThreshold &&
						s.stats &&
						s.stats.setWins < bracket.settings.advanceThreshold;
					const renderEliminatedRow =
						!eliminatedRowRendered &&
						bracket.settings?.advanceThreshold &&
						s.stats &&
						Swiss.calculateTeamStatus({
							advanceThreshold: bracket.settings.advanceThreshold,
							losses: s.stats.setLosses,
							wins: s.stats.setWins,
							roundCount:
								bracket.settings.roundCount ??
								TOURNAMENT.SWISS_DEFAULT_ROUND_COUNT,
						}) === "eliminated";

					if (renderQualifiedRow) qualifiedRowRendered = true;
					if (renderEliminatedRow) eliminatedRowRendered = true;

					return (
						<React.Fragment key={s.team.id}>
							{renderQualifiedRow ? (
								<SwissDividerRow
									key="qualified"
									type="qualified"
									threshold={bracket.settings!.advanceThreshold!}
									// TODO: get columns from the tiebreakers array
									columnCount={8 + (canEditDestination ? 1 : 0)}
								/>
							) : null}
							{renderEliminatedRow ? (
								<SwissDividerRow
									key="eliminated"
									type="eliminated"
									threshold={bracket.settings!.advanceThreshold!}
									// TODO: get columns from the tiebreakers array
									columnCount={8 + (canEditDestination ? 1 : 0)}
								/>
							) : null}
							<tr>
								<td>
									<Link
										to={tournamentTeamPage({
											tournamentId: bracket.tournament.ctx.id,
											tournamentTeamId: s.team.id,
										})}
										className={styles.teamNameLink}
										title={s.team.name}
									>
										{s.team.name}
									</Link>{" "}
									{s.team.droppedOut ? (
										<span className="text-warning text-xxxs font-bold">
											Drop-out
										</span>
									) : null}
								</td>
								<td>
									<span>
										{stats.setWins}/{stats.setLosses}
									</span>
								</td>
								{bracket.type === "round_robin" ? (
									<td>
										<span>{stats.winsAgainstTied}</span>
									</td>
								) : null}
								{bracket.type === "swiss" ? (
									<td>
										<span>{(stats.lossesAgainstTied ?? 0) * -1}</span>
									</td>
								) : null}
								{bracket.type === "swiss" ? (
									<td>
										<span>{stats.opponentSetWinPercentage?.toFixed(2)}</span>
									</td>
								) : null}
								<td>
									<span>
										{stats.mapWins}/{stats.mapLosses}
									</span>
								</td>
								{bracket.type === "swiss" ? (
									<td>
										<span>{stats.opponentMapWinPercentage?.toFixed(2)}</span>
									</td>
								) : null}
								{bracket.type === "round_robin" ? (
									<td>
										<span>{stats.koCount ?? 0}</span>
									</td>
								) : null}
								<td>{team?.seed}</td>
								<EditableDestination
									key={key()}
									source={bracket}
									destination={dest}
									overridenDestination={overridenDestinationBracket}
									possibleDestinations={possibleDestinationBrackets}
									allMatchesFinished={allMatchesFinished}
									canEditDestination={canEditDestination}
									tournamentTeamId={s.team.id}
									droppedOut={Boolean(s.team.droppedOut)}
								/>
							</tr>
							{!eliminatedRowRendered &&
							i === standings.length - 1 &&
							bracket.settings?.advanceThreshold ? (
								<SwissDividerRow
									key="eliminated"
									type="eliminated"
									threshold={bracket.settings.advanceThreshold}
									// TODO: get columns from the tiebreakers array
									columnCount={8 + (canEditDestination ? 1 : 0)}
								/>
							) : null}
						</React.Fragment>
					);
				})}
			</tbody>
		</table>
	);
}

function EditableDestination({
	source,
	destination,
	overridenDestination,
	possibleDestinations: _possibleDestinations,
	allMatchesFinished,
	canEditDestination,
	tournamentTeamId,
	droppedOut,
}: {
	source: Bracket;
	destination?: Bracket;
	overridenDestination?: Bracket | null;
	possibleDestinations: Bracket[];
	allMatchesFinished: boolean;
	canEditDestination: boolean;
	tournamentTeamId: number;
	droppedOut: boolean;
}) {
	const fetcher = useFetcher<any>();
	const [editingDestination, setEditingDestination] = React.useState(false);
	const [newDestinationIdx, setNewDestinationIdx] = React.useState<
		number | null
	>(overridenDestination?.idx ?? destination?.idx ?? -1);

	const handleSubmit = () => {
		fetcher.submit(
			{
				_action: "OVERRIDE_BRACKET_PROGRESSION",
				tournamentTeamId,
				sourceBracketIdx: source.idx,
				destinationBracketIdx: newDestinationIdx,
			},
			{ method: "post", encType: "application/json" },
		);
	};

	const possibleDestinations = [
		"ELIMINATED",
		..._possibleDestinations,
	] as const;

	if (editingDestination) {
		return (
			<>
				<td>
					<select
						value={String(newDestinationIdx)}
						onChange={(e) => setNewDestinationIdx(Number(e.target.value))}
					>
						{possibleDestinations.map((b) => (
							<option
								key={b === "ELIMINATED" ? "ELIMINATED" : b.id}
								value={b === "ELIMINATED" ? -1 : b.idx}
							>
								{b === "ELIMINATED" ? "Eliminated" : b.name}
							</option>
						))}
					</select>
				</td>
				<td>
					<div className="stack horizontal xs">
						<SendouButton
							variant="minimal"
							icon={<Check />}
							size="small"
							onPress={handleSubmit}
						/>
						<SendouButton
							variant="minimal-destructive"
							size="small"
							icon={<X />}
							onPress={() => setEditingDestination(false)}
						/>
					</div>
				</td>
			</>
		);
	}

	return (
		<>
			{droppedOut ? (
				<td />
			) : allMatchesFinished &&
				overridenDestination &&
				overridenDestination.idx !== destination?.idx ? (
				<td className="text-theme font-bold">
					<Link
						to={tournamentBracketsPage({
							tournamentId: source.tournament.ctx.id,
							bracketIdx: overridenDestination.idx,
						})}
						className={styles.destinationLink}
						defaultShouldRevalidate={false}
					>
						→ {overridenDestination.name}
					</Link>
				</td>
			) : destination && overridenDestination !== null ? (
				<td
					className={clsx({
						"italic text-lighter": !allMatchesFinished,
					})}
				>
					<Link
						to={tournamentBracketsPage({
							tournamentId: source.tournament.ctx.id,
							bracketIdx: destination.idx,
						})}
						className={styles.destinationLink}
						defaultShouldRevalidate={false}
					>
						→ {destination.name}
					</Link>
				</td>
			) : (
				<td />
			)}
			{canEditDestination && !droppedOut ? (
				<td>
					<SendouButton
						variant="minimal"
						icon={<SquarePen />}
						size="small"
						onPress={() => setEditingDestination(true)}
					/>
				</td>
			) : canEditDestination ? (
				<td />
			) : null}
		</>
	);
}

function SwissDividerRow({
	type,
	threshold,
	columnCount,
}: {
	type: "qualified" | "eliminated";
	threshold: number;
	columnCount: number;
}) {
	const isQualified = type === "qualified";
	const message = isQualified
		? `Qualified (@ ${threshold} wins)`
		: `Eliminated (@ ${threshold} losses)`;

	return (
		<tr className={styles.standingsDividerRow}>
			<td colSpan={columnCount} className={styles.standingsDivider}>
				<div
					className={clsx(styles.standingsDividerContent, {
						[styles.standingsDividerQualified]: isQualified,
						[styles.standingsDividerEliminated]: !isQualified,
					})}
				>
					<div className={styles.standingsDividerLine} />
					<span className={styles.standingsDividerText}>{message}</span>
					<div className={styles.standingsDividerLine} />
				</div>
			</td>
		</tr>
	);
}
