import { and, eq, isNotNull, lt, or, sql, desc as sqlDesc } from "drizzle-orm";
import Elysia, { t } from "elysia";
import { auth } from "~/auth";
import { db } from "~/db";
import { entries, profiles } from "~/db/schema";
import { watchlist } from "~/db/schema/watchlist";
import { getColumns } from "~/db/utils";
import { Entry } from "~/models/entry";
import { Show } from "~/models/show";
import {
	AcceptLanguage,
	createPage,
	Filter,
	type FilterDef,
	keysetPaginate,
	Page,
	processLanguages,
	Sort,
	sortToSql,
} from "~/models/utils";
import { desc } from "~/models/utils/descriptions";
import {
	entryFilters,
	entryProgressQ,
	entryRelations,
	entryVideosQ,
	getEntryTransQ,
	mapProgress,
} from "../entries";

const nextupSort = Sort(
	// copy pasted from entrySort + adding new stuff
	{
		order: entries.order,
		seasonNumber: entries.seasonNumber,
		episodeNumber: entries.episodeNumber,
		number: entries.episodeNumber,
		airDate: entries.airDate,

		started: watchlist.startedAt,
		added: watchlist.createdAt,
		lastPlayed: watchlist.lastPlayedAt,
	},
	{
		default: ["-lastPlayed"],
		tablePk: entries.pk,
	},
);

const nextupFilters: FilterDef = {
	...entryFilters,
};

export const nextup = new Elysia({ tags: ["profiles"] })
	.use(auth)
	.guard({
		query: t.Object({
			sort: nextupSort,
			filter: t.Optional(Filter({ def: nextupFilters })),
			query: t.Optional(t.String({ description: desc.query })),
			limit: t.Integer({
				minimum: 1,
				maximum: 250,
				default: 50,
				description: "Max page size.",
			}),
			after: t.Optional(t.String({ description: desc.after })),
		}),
	})
	.get(
		"/profiles/me/nextup",
		// @ts-expect-error the t.Composite breaks typecheck idk why
		async ({
			query: { sort, filter, query, limit, after },
			headers: { "accept-language": languages, ...headers },
			request: { url },
			jwt: { sub, settings },
		}) => {
			const langs = processLanguages(languages);
			const transQ = getEntryTransQ(langs);

			const {
				externalId,
				order,
				seasonNumber,
				episodeNumber,
				extraKind,
				kind,
				...entryCol
			} = getColumns(entries);

			const items = await db
				.select({
					...entryCol,
					...getColumns(transQ),
					__similarity: query
						? sql`word_similarity(${query}::text, ${transQ.name})`.as(
								"__similarity",
							)
						: sql`false`,
					videos: entryVideosQ.videos,
					progress: mapProgress({ aliased: true }),
					// specials don't have an `episodeNumber` but a `number` field.
					number: sql<number>`${episodeNumber}`,

					// assign more restrained types to make typescript happy.
					kind: sql<Entry["kind"]>`${kind}`,
					externalId: sql<any>`${externalId}`,
					order: sql<number>`${order}`,
					seasonNumber: sql<number>`${seasonNumber}`,
					episodeNumber: sql<number>`${episodeNumber}`,
					name: sql<string>`${transQ.name}`,

					show: sql`${entryRelations.show({
						languages: langs,
						preferOriginal: settings.preferOriginal,
					})}`,
				})
				.from(entries)
				.innerJoin(watchlist, eq(watchlist.nextEntry, entries.pk))
				.innerJoin(profiles, eq(watchlist.profilePk, profiles.pk))
				.innerJoin(transQ, eq(entries.pk, transQ.pk))
				.crossJoinLateral(entryVideosQ)
				.leftJoin(entryProgressQ, eq(entries.pk, entryProgressQ.entryPk))
				.where(
					and(
						eq(profiles.id, sub),
						or(
							lt(entries.airDate, sql`now()`),
							isNotNull(entries.availableSince),
						),
						filter,
						query ? sql`${transQ.name} %> ${query}::text` : undefined,
						keysetPaginate({ after, sort, query }),
					),
				)
				.orderBy(
					...(query ? [sqlDesc(sql`__similarity`)] : sortToSql(sort)),
					entries.pk,
				)
				.limit(limit)
				.execute({ userId: sub });

			return createPage(items, { url, sort, limit, headers, query });
		},
		{
			detail: {
				description: "",
			},
			headers: t.Object({
				"accept-language": AcceptLanguage({ autoFallback: true }),
			}),
			response: {
				200: Page(t.Intersect([Entry, t.Object({ show: Show })])),
			},
		},
	);
