import type { ActionFunctionArgs } from "react-router";
import { redirect } from "react-router";
import { requireUser } from "~/features/auth/core/user.server";
import * as ChatSystemMessage from "~/features/chat/ChatSystemMessage.server";
import * as Seasons from "~/features/mmr/core/Seasons";
import { notify } from "~/features/notifications/core/notify.server";
import * as SQGroupRepository from "~/features/sendouq/SQGroupRepository.server";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import { errorToastIfFalsy, parseRequestPayload } from "~/utils/remix.server";
import { assertUnreachable } from "~/utils/types";
import { SENDOUQ_LOOKING_PAGE } from "~/utils/urls";
import { refreshSendouQInstance, SendouQ } from "../core/SendouQ.server";
import { SENDOUQ_LOOKING_ROOM, sqGroupWebsocketRoom } from "../q-constants";
import { preparingSchema } from "../q-schemas.server";
import { SendouQError, setGroupChatMetadata } from "../q-utils.server";

export type SendouQPreparingAction = typeof action;

export const action = async ({ request }: ActionFunctionArgs) => {
	const user = requireUser();
	const data = await parseRequestPayload({
		request,
		schema: preparingSchema,
	});

	const ownGroup = SendouQ.findOwnGroup(user.id);
	errorToastIfFalsy(ownGroup, "No group found");

	// no perms, possibly just lost them so no more graceful degradation
	if (ownGroup.usersRole === "REGULAR") {
		return null;
	}

	const season = Seasons.current();
	errorToastIfFalsy(season, "Season is not active");

	try {
		switch (data._action) {
			case "JOIN_QUEUE": {
				await SQGroupRepository.setPreparingGroupAsActive(ownGroup.id);

				await refreshSendouQInstance();

				ChatSystemMessage.send({
					room: SENDOUQ_LOOKING_ROOM,
					revalidateOnly: true,
				});

				return redirect(SENDOUQ_LOOKING_PAGE);
			}
			case "ADD_FRIEND": {
				const available = await SQGroupRepository.findActiveGroupMembers();
				if (available.some(({ userId }) => userId === data.id)) {
					return { error: "taken" } as const;
				}

				errorToastIfFalsy(
					(await SQGroupRepository.friendsAndTeammates(user.id)).friends.some(
						(friendUser) => friendUser.id === data.id,
					),
					"Not a friend",
				);
				errorToastIfFalsy(
					(await UserRepository.findLeanById(data.id))?.friendCode,
					"User you are trying to add has no friend code set",
				);

				const { chatCodeToRevalidate } = await SQGroupRepository.addMember(
					ownGroup.id,
					{
						userId: data.id,
						role: "MANAGER",
					},
				);

				if (chatCodeToRevalidate) {
					ChatSystemMessage.send({
						room: chatCodeToRevalidate,
						revalidateOnly: true,
					});
				}

				await refreshSendouQInstance();

				const updatedGroup = SendouQ.findOwnGroup(user.id);
				if (updatedGroup?.chatCode) {
					setGroupChatMetadata({
						chatCode: updatedGroup.chatCode,
						members: updatedGroup.members,
					});
				}

				ChatSystemMessage.send({
					room: sqGroupWebsocketRoom(ownGroup.id),
					revalidateOnly: true,
				});

				notify({
					userIds: [data.id],
					notification: {
						type: "SQ_ADDED_TO_GROUP",
						meta: {
							adderUsername: user.username,
						},
					},
				});

				return null;
			}
			default: {
				assertUnreachable(data);
			}
		}
	} catch (error) {
		// some errors are expected to happen, for example two requests racing to
		// create/join a group. return null so loaders re-run and the user sees
		// the fresh state instead of an error page
		if (error instanceof SendouQError) {
			return null;
		}

		throw error;
	}
};
