import type { ActionFunction } from "react-router";
import { requireUser } from "~/features/auth/core/user.server";
import * as ShowcaseTournaments from "~/features/front-page/core/ShowcaseTournaments.server";
import { MapPool } from "~/features/map-list-generator/core/map-pool";
import { notify } from "~/features/notifications/core/notify.server";
import * as SQGroupRepository from "~/features/sendouq/SQGroupRepository.server";
import * as TeamRepository from "~/features/team/TeamRepository.server";
import * as SavedCalendarEventRepository from "~/features/tournament/SavedCalendarEventRepository.server";
import * as TournamentTeamRepository from "~/features/tournament/TournamentTeamRepository.server";
import {
	clearTournamentDataCache,
	tournamentFromDB,
} from "~/features/tournament-bracket/core/Tournament.server";
import * as TournamentLFGRepository from "~/features/tournament-lfg/TournamentLFGRepository.server";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import { parseFormDataWithImages } from "~/form/parse.server";
import { logger } from "~/utils/logger";
import { errorToastIfFalsy, parseParams } from "~/utils/remix.server";
import { assertUnreachable } from "~/utils/types";
import { idObject } from "~/utils/zod";
import { registerSchema } from "../tournament-schemas.server";
import {
	isOneModeTournamentOf,
	validateCounterPickMapPool,
} from "../tournament-utils";
import {
	requireNotBannedByOrganization,
	requireSendouQParticipationIfNeeded,
} from "../tournament-utils.server";

export const action: ActionFunction = async ({ request, params }) => {
	const user = requireUser();
	const { id: tournamentId } = parseParams({
		params,
		schema: idObject,
	});

	const tournament = await tournamentFromDB({ tournamentId, user });
	const ownTeam = tournament.ownedTeamByUser(user);

	const result = await parseFormDataWithImages({
		request,
		schema: registerSchema({ tournament, ownTeamId: ownTeam?.id }),
	});
	if (!result.success) {
		return { fieldErrors: result.fieldErrors };
	}
	const data = result.data;

	errorToastIfFalsy(
		!tournament.hasStarted,
		"Tournament has started, cannot make edits to registration",
	);

	const ownTeamCheckedIn = Boolean(ownTeam && ownTeam.checkIns.length > 0);

	switch (data._action) {
		case "UPSERT_TEAM": {
			const linkedTeamId = data.teamId ? Number(data.teamId) : null;

			errorToastIfFalsy(
				!linkedTeamId ||
					(await TeamRepository.findAllMemberOfByUserId(user.id)).some(
						(team) => team.id === linkedTeamId,
					),
				"Team id does not match any of the teams you are in",
			);

			// linked teams source their name and logo from the sendou.ink team
			const name = (
				linkedTeamId
					? (await TeamRepository.findById(linkedTeamId))?.name
					: data.pickUpName
			)!;

			const avatarImgId = linkedTeamId ? null : data.logo;

			if (ownTeam) {
				errorToastIfFalsy(
					tournament.registrationOpen || name === ownTeam.name,
					"Can't change team name after registration has closed",
				);

				await TournamentTeamRepository.update({
					avatarImgId,
					team: {
						id: ownTeam.id,
						name,
						prefersNotToHost: Number(data.prefersNotToHost),
						teamId: linkedTeamId,
					},
				});
			} else {
				await requireNotBannedByOrganization({
					tournament,
					user,
				});
				await requireSendouQParticipationIfNeeded({
					tournament,
					userId: user.id,
				});

				errorToastIfFalsy(!tournament.isInvitational, "Event is invite only");
				errorToastIfFalsy(
					(await UserRepository.findLeanById(user.id))?.friendCode,
					"No friend code",
				);
				errorToastIfFalsy(
					!tournament.teamMemberOfByUser(user),
					"You are already in a team that you aren't captain of",
				);
				errorToastIfFalsy(
					tournament.registrationOpen,
					"Registration is closed",
				);

				await TournamentLFGRepository.leaveLfg({
					userId: user.id,
					tournamentId,
				});
				await TournamentTeamRepository.create({
					team: {
						name,
						prefersNotToHost: Number(data.prefersNotToHost),
						teamId: linkedTeamId,
					},
					userId: user.id,
					tournamentId,
					avatarImgId,
				});
				await SavedCalendarEventRepository.unsave({
					userId: user.id,
					tournamentId,
				});

				ShowcaseTournaments.addToCached({
					tournamentId,
					type: "participant",
					userId: user.id,
					newTeamCount: tournament.ctx.teams.length + 1,
				});
			}
			break;
		}
		case "DELETE_TEAM_MEMBER": {
			errorToastIfFalsy(ownTeam, "You are not registered to this tournament");
			errorToastIfFalsy(
				ownTeam.members.some((member) => member.userId === data.userId),
				"User is not in your team",
			);
			errorToastIfFalsy(data.userId !== user.id, "Can't kick yourself");

			// making sure they aren't unfilling one checking in condition i.e. having full roster
			// and then having members kicked without it affecting the checking in status
			errorToastIfFalsy(
				!ownTeamCheckedIn ||
					ownTeam.members.length > tournament.minMembersPerTeam,
				"Can't kick a member after checking in",
			);

			await TournamentTeamRepository.leave({
				teamId: ownTeam.id,
				userId: data.userId,
			});

			ShowcaseTournaments.removeFromCached({
				tournamentId,
				type: "participant",
				userId: data.userId,
			});
			break;
		}
		case "LEAVE_TEAM": {
			errorToastIfFalsy(!ownTeam, "Can't leave a team as the owner");

			const teamMemberOf = tournament.teamMemberOfByUser(user);
			errorToastIfFalsy(teamMemberOf, "You are not in a team");
			errorToastIfFalsy(
				teamMemberOf.checkIns.length === 0,
				"You cannot leave after checking in",
			);
			errorToastIfFalsy(
				tournament.registrationOpen,
				"Registration has closed, contact the TO to leave the team",
			);

			await TournamentTeamRepository.leave({
				teamId: teamMemberOf.id,
				userId: user.id,
			});

			ShowcaseTournaments.removeFromCached({
				tournamentId,
				type: "participant",
				userId: user.id,
			});

			break;
		}
		case "UPDATE_MAP_POOL": {
			const mapPool = new MapPool(data.mapPool);
			errorToastIfFalsy(ownTeam, "You are not registered to this tournament");
			errorToastIfFalsy(
				validateCounterPickMapPool(
					mapPool,
					isOneModeTournamentOf(
						tournament.ctx.mapPickingStyle,
						tournament.ctx.toSetMapPool,
					),
					tournament.ctx.tieBreakerMapPool,
				) === "VALID",
				"Invalid map pool",
			);

			await TournamentTeamRepository.upsertCounterpickMaps({
				tournamentTeamId: ownTeam.id,
				mapPool: new MapPool(data.mapPool),
			});
			break;
		}
		case "CHECK_IN": {
			logger.info(
				`Checking in (try): owned tournament team id: ${ownTeam?.id} - user id: ${user.id} - tournament id: ${tournamentId}`,
			);

			const teamMemberOf = tournament.teamMemberOfByUser(user);
			errorToastIfFalsy(teamMemberOf, "You are not in a team");
			errorToastIfFalsy(
				teamMemberOf.checkIns.length === 0,
				"You have already checked in",
			);

			errorToastIfFalsy(
				tournament.regularCheckInIsOpen,
				"Check in is not open",
			);
			errorToastIfFalsy(
				tournament.checkInConditionsFulfilledByTeamId(teamMemberOf.id)
					.isFulfilled,
				`Can't check-in - ${tournament.checkInConditionsFulfilledByTeamId(teamMemberOf.id).reason}`,
			);

			await TournamentTeamRepository.checkIn(teamMemberOf.id);
			logger.info(
				`Checking in (success): tournament team id: ${teamMemberOf.id} - user id: ${user.id} - tournament id: ${tournamentId}`,
			);
			break;
		}
		case "ADD_PLAYER": {
			errorToastIfFalsy(
				tournament.ctx.teams.every((team) =>
					team.members.every((member) => member.userId !== data.userId),
				),
				"User is already in a team",
			);
			errorToastIfFalsy(ownTeam, "You are not registered to this tournament");
			errorToastIfFalsy(
				(await SQGroupRepository.friendsAndTeammates(user.id)).friends.some(
					(friendPlayer) => friendPlayer.id === data.userId,
				),
				"Not a friend",
			);
			errorToastIfFalsy(
				(await UserRepository.findLeanById(data.userId))?.friendCode,
				"User you are trying to add has no friend code set",
			);
			errorToastIfFalsy(tournament.registrationOpen, "Registration is closed");

			await requireNotBannedByOrganization({
				tournament,
				user: { id: data.userId },
				message: "The user is banned from events hosted by this organization",
			});
			await requireSendouQParticipationIfNeeded({
				tournament,
				userId: data.userId,
			});

			await TournamentLFGRepository.leaveLfg({
				userId: data.userId,
				tournamentId,
			});
			await TournamentTeamRepository.join({
				userId: data.userId,
				newTeamId: ownTeam.id,
			});

			await SavedCalendarEventRepository.unsave({
				userId: data.userId,
				tournamentId,
			});

			ShowcaseTournaments.addToCached({
				tournamentId,
				type: "participant",
				userId: data.userId,
			});

			if (!tournament.isTest && !tournament.isDraft) {
				notify({
					userIds: [data.userId],
					notification: {
						type: "TO_ADDED_TO_TEAM",
						meta: {
							adderUsername: user.username,
							tournamentId,
							teamName: ownTeam.name,
							tournamentName: tournament.ctx.name,
							tournamentTeamId: ownTeam.id,
						},
						pictureUrl: tournament.ctx.logoUrl,
					},
				});
			}

			break;
		}
		case "UNREGISTER": {
			errorToastIfFalsy(ownTeam, "You are not registered to this tournament");
			errorToastIfFalsy(
				!ownTeamCheckedIn,
				"You cannot unregister after checking in",
			);
			errorToastIfFalsy(
				!tournament.isLeagueSignup || tournament.registrationOpen,
				"Unregistering from leagues is not possible after registration has closed",
			);

			await TournamentTeamRepository.del(ownTeam.id);

			for (const member of ownTeam.members) {
				ShowcaseTournaments.removeFromCached({
					tournamentId,
					type: "participant",
					userId: member.userId,
				});

				ShowcaseTournaments.updateCachedTournamentTeamCount({
					tournamentId,
					newTeamCount: tournament.ctx.teams.length - 1,
				});
			}

			break;
		}
		default: {
			assertUnreachable(data);
		}
	}

	clearTournamentDataCache(tournamentId);

	return null;
};
