import { TRPCError } from "@trpc/server";
import { and, count, desc, eq, inArray, like, ne, or, sql } from "drizzle-orm";
import { database } from "@/db";
import {
	credits,
	episodes,
	genres,
	movies,
	people,
	seasons,
	series,
	titleGenres,
	titles,
} from "@/db/schema";
import { t } from "@/lib/trpc";
import {
	GetSeasonEpisodesInput,
	GetSeasonEpisodesOutput,
	GetTitleDetailsInput,
	GetTitleDetailsOutput,
	SearchTitlesInput,
	SearchTitlesOutput,
} from "@/models/trpc/titles";
import { authenticationMiddleware } from "../middleware/authentication";

const titleProcedure = t.procedure.use(authenticationMiddleware);

export const titleRouter = t.router({
	getDetails: titleProcedure
		.input(GetTitleDetailsInput)
		.output(GetTitleDetailsOutput)
		.query(async ({ input }) => {
			const { id } = input;

			// 1. Get title data (with type)
			const titleData = await database
				.select({
					id: titles.id,
					type: titles.type,
					title: titles.title,
					originalTitle: titles.originalTitle,
					year: titles.year,
					poster: titles.poster,
					backdrop: titles.backdrop,
					rating: titles.rating,
					synopsis: titles.overview,
					movieId: movies.id,
					runtimeSeconds: movies.runtimeSeconds,
					seriesId: series.id,
				})
				.from(titles)
				.leftJoin(movies, eq(movies.titleId, titles.id))
				.leftJoin(series, eq(series.titleId, titles.id))
				.where(eq(titles.id, id))
				.limit(1);

			if (titleData.length === 0) {
				throw new TRPCError({
					code: "NOT_FOUND",
					message: "Title not found",
				});
			}

			const title = titleData[0];

			// 2. Get genres
			const genresData = await database
				.select({
					id: genres.id,
					name: genres.name,
				})
				.from(titleGenres)
				.innerJoin(genres, eq(genres.id, titleGenres.genreId))
				.where(eq(titleGenres.titleId, id));

			// 3. Get cast (for movie or series)
			const castCondition = title.movieId
				? eq(credits.movieId, title.movieId)
				: title.seriesId
					? eq(credits.seriesId, title.seriesId)
					: sql`1 = 0`;

			const castData = await database
				.select({
					id: people.id,
					name: people.name,
					character: credits.character,
					profilePicture: people.profilePicture,
					order: credits.order,
				})
				.from(credits)
				.innerJoin(people, eq(people.id, credits.personId))
				.where(and(castCondition, eq(credits.role, "CAST")))
				.orderBy(credits.order);

			// 4. Get crew (for movie or series)
			const crewCondition = title.movieId
				? eq(credits.movieId, title.movieId)
				: title.seriesId
					? eq(credits.seriesId, title.seriesId)
					: sql`1 = 0`;

			const crewData = await database
				.select({
					id: people.id,
					name: people.name,
					job: credits.job,
					department: credits.department,
					profilePicture: people.profilePicture,
					order: credits.order,
				})
				.from(credits)
				.innerJoin(people, eq(people.id, credits.personId))
				.where(and(crewCondition, eq(credits.role, "CREW")))
				.orderBy(credits.order);

			// 5. Get similar titles (same genres, exclude current title)
			const genreIds = genresData.map((g) => g.id);
			const similarTitlesData =
				genreIds.length > 0
					? await database
							.select({
								id: titles.id,
								title: titles.title,
								year: titles.year,
								poster: titles.poster,
								matchCount: sql<number>`count(distinct ${titleGenres.genreId})`,
							})
							.from(titles)
							.innerJoin(titleGenres, eq(titleGenres.titleId, titles.id))
							.where(
								and(inArray(titleGenres.genreId, genreIds), ne(titles.id, id)),
							)
							.groupBy(titles.id)
							.orderBy(sql`count(distinct ${titleGenres.genreId}) DESC`)
							.limit(10)
					: [];

			// 6. Common response data
			const commonData = {
				id: title.id,
				title: title.title,
				originalTitle: title.originalTitle,
				year: title.year,
				poster: title.poster,
				backdrop: title.backdrop,
				rating: title.rating,
				genres: genresData,
				synopsis: title.synopsis,
				cast: castData,
				crew: crewData,
				similarTitles: similarTitlesData.map((t) => ({
					id: t.id,
					title: t.title,
					year: t.year,
					poster: t.poster,
				})),
			};

			// Return discriminated union based on type
			if (title.type === "MOVIE") {
				return {
					...commonData,
					type: "MOVIE" as const,
					runtimeSeconds: title.runtimeSeconds,
				};
			}

			// For SERIES: count seasons and episodes
			const seasonsCountData = title.seriesId
				? await database
						.select({ count: count() })
						.from(seasons)
						.where(eq(seasons.seriesId, title.seriesId))
				: [{ count: 0 }];

			const episodesCountData = title.seriesId
				? await database
						.select({ count: count() })
						.from(episodes)
						.where(eq(episodes.seriesId, title.seriesId))
				: [{ count: 0 }];

			// Get seasons list with episode count
			const seasonsData = title.seriesId
				? await database
						.select({
							seasonId: seasons.id,
							seasonNumber: seasons.seasonNumber,
							title: seasons.title,
							episodes: count(episodes.id),
						})
						.from(seasons)
						.leftJoin(episodes, eq(episodes.seasonId, seasons.id))
						.where(eq(seasons.seriesId, title.seriesId))
						.groupBy(seasons.id)
						.orderBy(seasons.seasonNumber)
				: [];

			return {
				...commonData,
				type: "SERIES" as const,
				seasonsCount: seasonsCountData[0].count,
				episodesCount: episodesCountData[0].count,
				seasons: seasonsData,
			};
		}),

	search: titleProcedure
		.input(SearchTitlesInput)
		.output(SearchTitlesOutput)
		.query(async ({ input }) => {
			const { query } = input;
			const searchPattern = `%${query}%`;

			const results = await database
				.select({
					id: titles.id,
					title: titles.title,
					year: titles.year,
					poster: titles.poster,
					type: titles.type,
				})
				.from(titles)
				.where(
					or(
						like(titles.title, searchPattern),
						like(titles.originalTitle, searchPattern),
					),
				)
				.orderBy(desc(titles.popularity), desc(titles.year))
				.limit(20);

			return results;
		}),

	getSeasonEpisodes: titleProcedure
		.input(GetSeasonEpisodesInput)
		.output(GetSeasonEpisodesOutput)
		.query(async ({ input }) => {
			const { titleId, seasonId } = input;

			// 1. Validate title exists and is a SERIES
			const titleData = await database
				.select({
					id: titles.id,
					type: titles.type,
					seriesId: series.id,
				})
				.from(titles)
				.leftJoin(series, eq(series.titleId, titles.id))
				.where(eq(titles.id, titleId))
				.limit(1);

			if (titleData.length === 0) {
				throw new TRPCError({
					code: "NOT_FOUND",
					message: "Title not found",
				});
			}

			const title = titleData[0];

			if (title.type !== "SERIES") {
				throw new TRPCError({
					code: "BAD_REQUEST",
					message: "Title is not a series",
				});
			}

			if (!title.seriesId) {
				throw new TRPCError({
					code: "INTERNAL_SERVER_ERROR",
					message: "Series data not found",
				});
			}

			// 2. Validate season exists and belongs to this series
			const seasonData = await database
				.select({
					id: seasons.id,
					seasonNumber: seasons.seasonNumber,
					title: seasons.title,
					seriesId: seasons.seriesId,
				})
				.from(seasons)
				.where(eq(seasons.id, seasonId))
				.limit(1);

			if (seasonData.length === 0) {
				throw new TRPCError({
					code: "NOT_FOUND",
					message: "Season not found",
				});
			}

			const season = seasonData[0];

			// Verify season belongs to the correct series
			if (season.seriesId !== title.seriesId) {
				throw new TRPCError({
					code: "BAD_REQUEST",
					message: "Season does not belong to this series",
				});
			}

			// 3. Get episodes
			const episodesData = await database
				.select({
					id: episodes.id,
					episodeNumber: episodes.episodeNumber,
					title: episodes.title,
					overview: episodes.overview,
					airDate: episodes.airDate,
					runtimeSeconds: episodes.runtimeSeconds,
					backgroundPath: episodes.backgroundPath,
				})
				.from(episodes)
				.where(eq(episodes.seasonId, seasonId))
				.orderBy(episodes.episodeNumber);

			return {
				seasonId: season.id,
				seasonNumber: season.seasonNumber,
				seasonTitle: season.title,
				episodeCount: episodesData.length,
				episodes: episodesData.map((ep) => ({
					id: ep.id,
					episodeNumber: ep.episodeNumber,
					title: ep.title,
					overview: ep.overview,
					airDate: ep.airDate,
					runtimeSeconds: ep.runtimeSeconds,
					backgroundUrl: ep.backgroundPath,
				})),
			};
		}),
});
