import { formatDistance } from "date-fns";
import type { Insertable, NotNull } from "kysely";
import { jsonObjectFrom } from "kysely/helpers/sqlite";
import { db } from "~/db/sql";
import type { DB } from "~/db/tables";
import type { MonthYear } from "~/features/plus-voting/core";
import { databaseTimestampNow, databaseTimestampToDate } from "~/utils/dates";
import { commonUserSelect } from "~/utils/kysely.server";
import type { Unwrapped } from "~/utils/types";

export type FindAllByMonthItem = Unwrapped<typeof findAllByMonth>;
export async function findAllByMonth(args: MonthYear) {
	const allRows = await db
		.selectFrom("PlusSuggestion")
		.select(({ eb }) => [
			"PlusSuggestion.id",
			"PlusSuggestion.createdAt",
			"PlusSuggestion.updatedAt",
			"PlusSuggestion.text",
			"PlusSuggestion.tier",
			jsonObjectFrom(
				eb
					.selectFrom("User")
					.select((eb) => commonUserSelect(eb))
					.whereRef("PlusSuggestion.authorId", "=", "User.id"),
			).as("author"),
			jsonObjectFrom(
				eb
					.selectFrom("User")
					.leftJoin("PlusTier", "PlusSuggestion.suggestedId", "PlusTier.userId")
					.select((eb) => [
						...commonUserSelect(eb),
						"User.bio",
						"PlusTier.tier as plusTier",
					])
					.whereRef("PlusSuggestion.suggestedId", "=", "User.id"),
			).as("suggested"),
		])
		.where("PlusSuggestion.month", "=", args.month)
		.where("PlusSuggestion.year", "=", args.year)
		.orderBy("PlusSuggestion.createdAt", "asc")
		.$narrowType<{ author: NotNull; suggested: NotNull }>()
		.execute();

	// filter out suggestions that were made in the time period
	// between voting ending and people gaining access from the leaderboard
	const rows = allRows.filter(
		(r) => !r.suggested.plusTier || r.suggested.plusTier > r.tier,
	);

	type Row = (typeof rows)[number];

	const result: Array<{
		suggested: Row["suggested"];
		tier: Row["tier"];
		entries: Array<{
			author: Row["author"];
			createdAtRelative: string;
			createdAt: number;
			updatedAt: number | null;
			updatedAtRelative: string | null;
			id: Row["id"];
			text: Row["text"];
		}>;
	}> = [];

	for (const row of rows) {
		const existing = result.find(
			(r) => r.tier === row.tier && row.suggested.id === r.suggested.id,
		);

		const entry = {
			id: row.id,
			text: row.text,
			createdAtRelative: formatDistance(
				databaseTimestampToDate(row.createdAt),
				new Date(),
				{ addSuffix: true },
			),
			createdAt: row.createdAt,
			updatedAt: row.updatedAt,
			updatedAtRelative: row.updatedAt
				? formatDistance(databaseTimestampToDate(row.updatedAt), new Date(), {
						addSuffix: true,
					})
				: null,
			author: row.author,
		};
		if (existing) {
			existing.entries.push(entry);
		} else {
			result.push({
				tier: row.tier,
				suggested: row.suggested,
				entries: [entry],
			});
		}
	}

	return result.sort((a, b) => b.entries[0].createdAt - a.entries[0].createdAt);
}

export function create(args: Insertable<DB["PlusSuggestion"]>) {
	return db.insertInto("PlusSuggestion").values(args).execute();
}

export function updateTextById(id: number, text: string) {
	return db
		.updateTable("PlusSuggestion")
		.set({ text, updatedAt: databaseTimestampNow() })
		.where("id", "=", id)
		.execute();
}

export function deleteById(id: number) {
	return db.deleteFrom("PlusSuggestion").where("id", "=", id).execute();
}

export function deleteWithCommentsBySuggestedUserId({
	tier,
	userId,
	month,
	year,
}: {
	tier: number;
	userId: number;
	month: number;
	year: number;
}) {
	return db
		.deleteFrom("PlusSuggestion")
		.where("PlusSuggestion.suggestedId", "=", userId)
		.where("PlusSuggestion.tier", "=", tier)
		.where("PlusSuggestion.month", "=", month)
		.where("PlusSuggestion.year", "=", year)
		.execute();
}
