import type { FileUpload } from "@remix-run/form-data-parser";
import { nanoid } from "nanoid";
import type { ActionFunction } from "react-router";
import { redirect } from "react-router";
import * as ArtRepository from "~/features/art/ArtRepository.server";
import { requireUser } from "~/features/auth/core/user.server";
import { uploadStreamToS3 } from "~/features/img-upload/s3.server";
import { ALLOWED_IMAGE_EXTENSIONS } from "~/features/img-upload/upload-constants";
import { notify } from "~/features/notifications/core/notify.server";
import { requireRole } from "~/modules/permissions/guards.server";
import { dateToDatabaseTimestamp } from "~/utils/dates";
import invariant from "~/utils/invariant";
import {
	errorToastIfFalsy,
	parseFormData,
	parseRequestPayload,
	safeParseMultipartFormData,
} from "~/utils/remix.server";
import { userArtPage } from "~/utils/urls";
import { NEW_ART_EXISTING_SEARCH_PARAM_KEY } from "../art-constants";
import { editArtSchema, newArtSchema } from "../art-schemas.server";

export const action: ActionFunction = async ({ request, url }) => {
	const user = requireUser();
	requireRole("ARTIST");

	const searchParams = url.searchParams;
	const artIdRaw = searchParams.get(NEW_ART_EXISTING_SEARCH_PARAM_KEY);

	// updating logic
	if (artIdRaw) {
		const artId = Number(artIdRaw);

		const userArts = await ArtRepository.findArtsByUserId(user.id, {
			includeTagged: false,
		});
		const existingArt = userArts.find((art) => art.id === artId);
		errorToastIfFalsy(existingArt, "Art author is someone else");

		const data = await parseRequestPayload({
			request,
			schema: editArtSchema,
		});

		const editedArtId = await ArtRepository.update(artId, {
			description: data.description,
			isShowcase: data.isShowcase,
			linkedUsers: data.linkedUsers,
			tags: data.tags,
		});

		const existingLinkedUserIds =
			existingArt.linkedUsers?.map((u) => u.id) ?? [];
		const newLinkedUsers = data.linkedUsers.filter(
			(userId) => !existingLinkedUserIds.includes(userId),
		);

		notify({
			userIds: newLinkedUsers,
			notification: {
				type: "TAGGED_TO_ART",
				meta: {
					adderUsername: user.username,
					adderDiscordId: user.discordId,
					artId: editedArtId,
				},
			},
		});
	} else {
		const preDecidedFilename = `art-${nanoid()}-${Date.now()}`;

		const uploadHandler = async (fileUpload: FileUpload) => {
			if (
				fileUpload.fieldName === "img" ||
				fileUpload.fieldName === "smallImg"
			) {
				const ending = fileUpload.name.split(".").pop()?.toLowerCase();
				invariant(
					ending && ending !== fileUpload.name,
					`File missing extension: "${fileUpload.name}"`,
				);
				invariant(
					ALLOWED_IMAGE_EXTENSIONS.includes(ending),
					`Invalid file extension: "${ending}"`,
				);
				const newFilename = `${preDecidedFilename}${fileUpload.fieldName === "smallImg" ? "-small" : ""}.${ending}`;

				const uploadedFileLocation = await uploadStreamToS3(
					fileUpload.stream(),
					newFilename,
				);
				return uploadedFileLocation;
			}
			return null;
		};

		const formData = await safeParseMultipartFormData(
			request,
			// 5MB
			{ maxFileSize: 5 * 1024 * 1024 },
			uploadHandler,
		);
		const imgSrc = formData.get("img") as string | null;
		invariant(imgSrc);

		const urlParts = imgSrc.split("/");
		const fileName = urlParts[urlParts.length - 1];
		invariant(fileName);

		const data = await parseFormData({
			formData,
			schema: newArtSchema,
		});

		const addedArt = await ArtRepository.insert({
			description: data.description,
			url: fileName,
			validatedAt: user.patronTier ? dateToDatabaseTimestamp(new Date()) : null,
			linkedUsers: data.linkedUsers,
			tags: data.tags,
		});

		notify({
			userIds: data.linkedUsers,
			notification: {
				type: "TAGGED_TO_ART",
				meta: {
					adderUsername: user.username,
					adderDiscordId: user.discordId,
					artId: addedArt.id,
				},
			},
		});
	}

	throw redirect(userArtPage(user));
};
