import { db } from "@/db/client";
import { classics, externalIds, mediaItems } from "@/db/schema";
import { and, eq } from "drizzle-orm";

const TMDB_BASE_URL = "https://api.themoviedb.org/3";
const TMDB_IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w342";
const TMDB_BACKDROP_BASE_URL = "https://image.tmdb.org/t/p/w780";
const TMDB_PROFILE_BASE_URL = "https://image.tmdb.org/t/p/w185";

export type CatalogKind = "television" | "movies" | "music";

export type CatalogItem = {
  id: string;
  source: "tmdb";
  kind: Exclude<CatalogKind, "music">;
  title: string;
  year: string | null;
  overview: string | null;
  posterUrl: string | null;
  backdropUrl: string | null;
  rating: number | null;
  popularity: number | null;
  releaseDate: string | null;
  isClassic?: boolean;
};

export type CatalogPerson = {
  id: string;
  name: string;
  role: string | null;
  profileUrl: string | null;
};

export type CatalogDetails = CatalogItem & {
  tagline: string | null;
  status: string | null;
  runtime: string | null;
  genres: string[];
  cast: CatalogPerson[];
  creators: CatalogPerson[];
  networks: string[];
  seasonCount: number | null;
  episodeCount: number | null;
  homepage: string | null;
};

type TmdbMovie = {
  id: number;
  title?: string;
  release_date?: string;
  overview?: string;
  poster_path?: string | null;
  backdrop_path?: string | null;
  vote_average?: number;
  popularity?: number;
};

type TmdbTv = {
  id: number;
  name?: string;
  first_air_date?: string;
  overview?: string;
  poster_path?: string | null;
  backdrop_path?: string | null;
  vote_average?: number;
  popularity?: number;
};

type TmdbListResponse<T> = {
  results?: T[];
};

type TmdbPerson = {
  id?: number;
  name?: string;
  character?: string;
  job?: string;
  profile_path?: string | null;
};

type TmdbGenre = {
  name?: string;
};

type TmdbNetwork = {
  name?: string;
};

type TmdbMovieDetails = TmdbMovie & {
  tagline?: string;
  status?: string;
  runtime?: number;
  genres?: TmdbGenre[];
  homepage?: string;
  credits?: { cast?: TmdbPerson[]; crew?: TmdbPerson[] };
};

type TmdbTvDetails = TmdbTv & {
  tagline?: string;
  status?: string;
  episode_run_time?: number[];
  genres?: TmdbGenre[];
  homepage?: string;
  created_by?: TmdbPerson[];
  networks?: TmdbNetwork[];
  number_of_seasons?: number;
  number_of_episodes?: number;
  credits?: { cast?: TmdbPerson[]; crew?: TmdbPerson[] };
};

const tmdbCredential = process.env.MOVIEDB_API;

function tmdbHeaders(): HeadersInit {
  if (tmdbCredential?.startsWith("eyJ")) {
    return { Authorization: `Bearer ${tmdbCredential}` };
  }

  return {};
}

function withTmdbApiKey(url: URL) {
  if (!tmdbCredential || tmdbCredential.startsWith("eyJ")) {
    return url;
  }

  url.searchParams.set("api_key", tmdbCredential);
  return url;
}

async function fetchTmdb<T>(path: string, searchParams: Record<string, string>) {
  if (!tmdbCredential) {
    throw new Error("Missing MOVIEDB_API in .env.local");
  }

  const url = withTmdbApiKey(new URL(`${TMDB_BASE_URL}${path}`));
  for (const [key, value] of Object.entries(searchParams)) {
    url.searchParams.set(key, value);
  }

  const response = await fetch(url, {
    headers: tmdbHeaders(),
    next: { revalidate: 900 },
  });

  if (!response.ok) {
    throw new Error(`TMDB request failed: ${response.status} ${response.statusText}`);
  }

  return (await response.json()) as T;
}

function imageUrl(path?: string | null) {
  return path ? `${TMDB_IMAGE_BASE_URL}${path}` : null;
}

function backdropUrl(path?: string | null) {
  return path ? `${TMDB_BACKDROP_BASE_URL}${path}` : null;
}

function profileUrl(path?: string | null) {
  return path ? `${TMDB_PROFILE_BASE_URL}${path}` : null;
}

function yearFrom(date?: string) {
  return date ? date.slice(0, 4) : null;
}

function normalizeMovie(movie: TmdbMovie): CatalogItem {
  const releaseDate = movie.release_date ?? null;

  return {
    id: String(movie.id),
    source: "tmdb",
    kind: "movies",
    title: movie.title ?? "Untitled movie",
    year: yearFrom(movie.release_date),
    overview: movie.overview || null,
    posterUrl: imageUrl(movie.poster_path),
    backdropUrl: backdropUrl(movie.backdrop_path),
    rating: typeof movie.vote_average === "number" ? movie.vote_average : null,
    popularity: typeof movie.popularity === "number" ? movie.popularity : null,
    releaseDate,
  };
}

function normalizeTv(show: TmdbTv): CatalogItem {
  const releaseDate = show.first_air_date ?? null;

  return {
    id: String(show.id),
    source: "tmdb",
    kind: "television",
    title: show.name ?? "Untitled series",
    year: yearFrom(show.first_air_date),
    overview: show.overview || null,
    posterUrl: imageUrl(show.poster_path),
    backdropUrl: backdropUrl(show.backdrop_path),
    rating: typeof show.vote_average === "number" ? show.vote_average : null,
    popularity: typeof show.popularity === "number" ? show.popularity : null,
    releaseDate,
  };
}

async function withClassicFlags(kind: Exclude<CatalogKind, "music">, items: CatalogItem[]) {
  if (!items.length) {
    return items;
  }

  const mediaType = kind === "movies" ? "movie" : "tv_series";
  const rows = await db
    .select({
      title: mediaItems.title,
      year: mediaItems.year,
      externalId: externalIds.externalId,
    })
    .from(classics)
    .innerJoin(mediaItems, eq(mediaItems.id, classics.mediaItemId))
    .leftJoin(externalIds, and(eq(externalIds.mediaItemId, mediaItems.id), eq(externalIds.source, "tmdb")))
    .where(eq(mediaItems.mediaType, mediaType));

  const externalIdSet = new Set(rows.map((row) => row.externalId).filter((value): value is string => Boolean(value)));
  const titleYearSet = new Set(rows.map((row) => row.title.toLocaleLowerCase("en-US") + "::" + (row.year ?? "")));

  return items.map((item) => {
    const titleYearKey = item.title.toLocaleLowerCase("en-US") + "::" + (item.year ?? "");
    return externalIdSet.has(item.id) || titleYearSet.has(titleYearKey)
      ? { ...item, isClassic: true }
      : item;
  });
}

export async function getCatalogItems(kind: CatalogKind, query: string) {
  const trimmedQuery = query.trim();

  if (kind === "music") {
    return [];
  }

  if (kind === "movies") {
    const path = trimmedQuery ? "/search/movie" : "/movie/popular";
    const response = await fetchTmdb<TmdbListResponse<TmdbMovie>>(path, {
      ...(trimmedQuery ? { query: trimmedQuery } : {}),
      include_adult: "false",
      language: "en-US",
      page: "1",
    });

    return withClassicFlags(kind, (response.results ?? []).slice(0, 18).map(normalizeMovie));
  }

  const path = trimmedQuery ? "/search/tv" : "/tv/popular";
  const response = await fetchTmdb<TmdbListResponse<TmdbTv>>(path, {
    ...(trimmedQuery ? { query: trimmedQuery } : {}),
    include_adult: "false",
    language: "en-US",
    page: "1",
  });

  return withClassicFlags(kind, (response.results ?? []).slice(0, 18).map(normalizeTv));
}


function normalizePerson(person: TmdbPerson): CatalogPerson | null {
  if (!person.name) {
    return null;
  }

  return {
    id: String(person.id ?? person.name),
    name: person.name,
    role: person.character ?? person.job ?? null,
    profileUrl: profileUrl(person.profile_path),
  };
}

function peopleFrom(list: TmdbPerson[] | undefined, limit: number) {
  return (list ?? [])
    .map(normalizePerson)
    .filter((person): person is CatalogPerson => person !== null)
    .slice(0, limit);
}

function minutesToRuntime(minutes?: number) {
  if (!minutes) {
    return null;
  }

  const hours = Math.floor(minutes / 60);
  const remainingMinutes = minutes % 60;
  return hours ? hours + "h " + remainingMinutes + "m" : remainingMinutes + "m";
}

export async function getCatalogDetails(kind: Exclude<CatalogKind, "music">, id: string): Promise<CatalogDetails> {
  if (kind === "movies") {
    const movie = await fetchTmdb<TmdbMovieDetails>("/movie/" + encodeURIComponent(id), {
      append_to_response: "credits,external_ids",
      language: "en-US",
    });
    const item = normalizeMovie(movie);

    return {
      ...item,
      tagline: movie.tagline || null,
      status: movie.status || null,
      runtime: minutesToRuntime(movie.runtime),
      genres: (movie.genres ?? []).map((genre) => genre.name).filter((name): name is string => Boolean(name)),
      cast: peopleFrom(movie.credits?.cast, 10),
      creators: peopleFrom(movie.credits?.crew?.filter((person) => person.job === "Director"), 4),
      networks: [],
      seasonCount: null,
      episodeCount: null,
      homepage: movie.homepage || null,
    };
  }

  const show = await fetchTmdb<TmdbTvDetails>("/tv/" + encodeURIComponent(id), {
    append_to_response: "credits,external_ids",
    language: "en-US",
  });
  const item = normalizeTv(show);

  return {
    ...item,
    tagline: show.tagline || null,
    status: show.status || null,
    runtime: minutesToRuntime(show.episode_run_time?.[0]),
    genres: (show.genres ?? []).map((genre) => genre.name).filter((name): name is string => Boolean(name)),
    cast: peopleFrom(show.credits?.cast, 10),
    creators: peopleFrom(show.created_by, 4),
    networks: (show.networks ?? []).map((network) => network.name).filter((name): name is string => Boolean(name)),
    seasonCount: show.number_of_seasons ?? null,
    episodeCount: show.number_of_episodes ?? null,
    homepage: show.homepage || null,
  };
}
