import { TRPCError } from "@trpc/server";
import { and, desc, eq, gte } from "drizzle-orm";
import { z } from "zod";
import { database } from "@/db";
import {
	episodes,
	mediaItems,
	mediaStreams,
	movies,
	titles,
} from "@/db/schema";
import { t } from "@/lib/trpc";
import { GetMediaTracksInput, GetMediaTracksOutput } from "@/models/trpc/media";
import { authenticationMiddleware } from "@/trpc/middleware/authentication";

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

const GetRecentlyAddedInput = z.object({
	limit: z.number(),
});

const GetRecentlyAddedOutput = z.array(
	z.object({
		id: z.string(),
		title: z.string(),
		year: z.number().nullable(),
		poster: z.string().nullable(),
		createdAt: z.date(),
	}),
);

export const mediaRouter = t.router({
	getRecentlyAdded: mediaProcedure
		.input(GetRecentlyAddedInput)
		.output(GetRecentlyAddedOutput)
		.query(async ({ ctx, input }) => {
			const { limit } = input;

			// Calculate the date 7 days ago from now
			const sevenDaysAgo = new Date();
			sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);

			// Query titles (movies and series) that were created in the last 7 days
			const recentlyAddedTitles = await database
				.select({
					id: titles.id,
					title: titles.title,
					year: titles.year,
					poster: titles.poster,
					createdAt: titles.createdAt,
				})
				.from(titles)
				.where(gte(titles.createdAt, sevenDaysAgo))
				.orderBy(desc(titles.createdAt))
				.limit(limit);

			return recentlyAddedTitles;
		}),
	getNextUp: mediaProcedure.query(async ({ ctx }) => {}),
	getTracks: mediaProcedure
		.input(GetMediaTracksInput)
		.output(GetMediaTracksOutput)
		.query(async ({ input }) => {
			const { titleId, episodeId } = input;

			// Query media items and streams based on input type
			let mediaData: Array<{
				mediaItemId: number;
				path: string;
				container: string | null;
				sizeBytes: number | null;
				streamId: number;
				streamIndex: number;
				codecType: string;
				codecName: string | null;
				codecLongName: string | null;
				profile: string | null;
				bitRate: number | null;
				width: number | null;
				height: number | null;
				frameRate: number | null;
				channels: number | null;
				channelLayout: string | null;
				sampleRate: number | null;
				language: string | null;
				title: string | null;
				isDefault: boolean;
				isForced: boolean;
			}> = [];

			if (titleId) {
				// Check if title exists and get its type
				const titleData = await database
					.select({
						id: titles.id,
						type: titles.type,
						movieId: movies.id,
					})
					.from(titles)
					.leftJoin(movies, eq(movies.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 !== "MOVIE") {
					throw new TRPCError({
						code: "BAD_REQUEST",
						message: "Use episodeId for series content",
					});
				}

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

				// Query for movie
				mediaData = await database
					.select({
						mediaItemId: mediaItems.id,
						path: mediaItems.path,
						container: mediaItems.container,
						sizeBytes: mediaItems.sizeBytes,
						streamId: mediaStreams.id,
						streamIndex: mediaStreams.streamIndex,
						codecType: mediaStreams.codecType,
						codecName: mediaStreams.codecName,
						codecLongName: mediaStreams.codecLongName,
						profile: mediaStreams.profile,
						bitRate: mediaStreams.bitRate,
						width: mediaStreams.width,
						height: mediaStreams.height,
						frameRate: mediaStreams.frameRate,
						channels: mediaStreams.channels,
						channelLayout: mediaStreams.channelLayout,
						sampleRate: mediaStreams.sampleRate,
						language: mediaStreams.language,
						title: mediaStreams.title,
						isDefault: mediaStreams.isDefault,
						isForced: mediaStreams.isForced,
					})
					.from(mediaItems)
					.innerJoin(mediaStreams, eq(mediaStreams.mediaItemId, mediaItems.id))
					.where(
						and(
							eq(mediaItems.parentType, "MOVIE"),
							eq(mediaItems.movieId, title.movieId),
						),
					)
					.orderBy(mediaItems.id, mediaStreams.streamIndex);
			} else if (episodeId) {
				// Validate episode exists
				const episodeData = await database
					.select({
						id: episodes.id,
					})
					.from(episodes)
					.where(eq(episodes.id, episodeId))
					.limit(1);

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

				// Query for episode
				mediaData = await database
					.select({
						mediaItemId: mediaItems.id,
						path: mediaItems.path,
						container: mediaItems.container,
						sizeBytes: mediaItems.sizeBytes,
						streamId: mediaStreams.id,
						streamIndex: mediaStreams.streamIndex,
						codecType: mediaStreams.codecType,
						codecName: mediaStreams.codecName,
						codecLongName: mediaStreams.codecLongName,
						profile: mediaStreams.profile,
						bitRate: mediaStreams.bitRate,
						width: mediaStreams.width,
						height: mediaStreams.height,
						frameRate: mediaStreams.frameRate,
						channels: mediaStreams.channels,
						channelLayout: mediaStreams.channelLayout,
						sampleRate: mediaStreams.sampleRate,
						language: mediaStreams.language,
						title: mediaStreams.title,
						isDefault: mediaStreams.isDefault,
						isForced: mediaStreams.isForced,
					})
					.from(mediaItems)
					.innerJoin(mediaStreams, eq(mediaStreams.mediaItemId, mediaItems.id))
					.where(
						and(
							eq(mediaItems.parentType, "EPISODE"),
							eq(mediaItems.episodeId, episodeId),
						),
					)
					.orderBy(mediaItems.id, mediaStreams.streamIndex);
			}

			// Group by media item and organize tracks by type
			const mediaItemsMap = new Map<
				number,
				{
					mediaItem: {
						id: number;
						path: string;
						container: string | null;
						sizeBytes: number | null;
					};
					tracks: {
						video: Array<{
							streamIndex: number;
							codecName: string | null;
							codecLongName: string | null;
							profile: string | null;
							width: number | null;
							height: number | null;
							frameRate: number | null;
							bitRate: number | null;
						}>;
						audio: Array<{
							streamIndex: number;
							codecName: string | null;
							codecLongName: string | null;
							language: string | null;
							title: string | null;
							channels: number | null;
							channelLayout: string | null;
							sampleRate: number | null;
							bitRate: number | null;
							isDefault: boolean;
						}>;
						subtitle: Array<{
							streamIndex: number;
							codecName: string | null;
							codecLongName: string | null;
							language: string | null;
							title: string | null;
							isDefault: boolean;
							isForced: boolean;
						}>;
					};
				}
			>();

			for (const row of mediaData) {
				if (!mediaItemsMap.has(row.mediaItemId)) {
					mediaItemsMap.set(row.mediaItemId, {
						mediaItem: {
							id: row.mediaItemId,
							path: row.path,
							container: row.container,
							sizeBytes: row.sizeBytes,
						},
						tracks: {
							video: [],
							audio: [],
							subtitle: [],
						},
					});
				}

				const item = mediaItemsMap.get(row.mediaItemId);
				if (!item) continue;

				// Add track based on codec type
				if (row.codecType === "VIDEO") {
					// Filter out image/cover art streams (e.g., mjpeg, png, bmp)
					const imageCodecs = [
						"mjpeg",
						"png",
						"bmp",
						"gif",
						"webp",
						"jpeg",
						"jpg",
					];
					const isImageStream =
						row.codecName && imageCodecs.includes(row.codecName.toLowerCase());

					// Only include actual video streams (not embedded images/cover art)
					if (!isImageStream) {
						item.tracks.video.push({
							streamIndex: row.streamIndex,
							codecName: row.codecName,
							codecLongName: row.codecLongName,
							profile: row.profile,
							width: row.width,
							height: row.height,
							frameRate: row.frameRate,
							bitRate: row.bitRate,
						});
					}
				} else if (row.codecType === "AUDIO") {
					item.tracks.audio.push({
						streamIndex: row.streamIndex,
						codecName: row.codecName,
						codecLongName: row.codecLongName,
						language: row.language,
						title: row.title,
						channels: row.channels,
						channelLayout: row.channelLayout,
						sampleRate: row.sampleRate,
						bitRate: row.bitRate,
						isDefault: row.isDefault,
					});
				} else if (row.codecType === "SUBTITLE") {
					item.tracks.subtitle.push({
						streamIndex: row.streamIndex,
						codecName: row.codecName,
						codecLongName: row.codecLongName,
						language: row.language,
						title: row.title,
						isDefault: row.isDefault,
						isForced: row.isForced,
					});
				}
			}

			return Array.from(mediaItemsMap.values());
		}),
});
