import { readSecret } from '../lib/sensitive-storage'

const BASE_URL = 'https://api.themoviedb.org/3'
const IMG_BASE = 'https://image.tmdb.org/t/p'

/* ────────────────────────────────────────────────────────────── */
/*  Types                                                          */
/* ────────────────────────────────────────────────────────────── */

export interface TmdbProductionCompany {
  id: number
  name: string
  logo_path: string | null
  origin_country: string
}

export interface TmdbNetwork {
  id: number
  name: string
  logo_path: string | null
  origin_country: string
}

export interface TmdbCreator {
  id: number
  credit_id: string
  name: string
  gender: number
  profile_path: string | null
}

export interface TmdbSpokenLanguage {
  iso_639_1: string
  english_name: string
  name: string
}

export interface TmdbCountry {
  iso_3166_1: string
  name: string
}

export interface TmdbGenre {
  id: number
  name: string
}

export interface TmdbCollectionRef {
  id: number
  name: string
  poster_path: string | null
  backdrop_path: string | null
}

export interface TmdbCollection {
  id: number
  name: string
  overview: string
  poster_path: string | null
  backdrop_path: string | null
  parts: TmdbMovie[]
}

export interface TmdbVideo {
  id: string
  iso_639_1: string
  iso_3166_1: string
  key: string
  name: string
  site: string
  size: number
  type: string
  official: boolean
  published_at: string
}

export interface TmdbCastMember {
  id: number
  name: string
  character: string
  profile_path: string | null
  order: number
  cast_id?: number
  credit_id?: string
  gender?: number
  known_for_department?: string
  popularity?: number
  department?: string
  job?: string
}

export interface TmdbAggregateRole {
  credit_id: string
  character: string
  episode_count: number
}

export interface TmdbAggregateCastMember {
  id: number
  name: string
  profile_path: string | null
  roles: TmdbAggregateRole[]
  total_episode_count: number
  order: number
  gender?: number
}

export interface TmdbAggregateJob {
  credit_id: string
  job: string
  episode_count: number
}

export interface TmdbAggregateCrewMember {
  id: number
  name: string
  profile_path: string | null
  jobs: TmdbAggregateJob[]
  total_episode_count: number
  department: string
  gender?: number
}

export interface TmdbCredits {
  cast: TmdbCastMember[]
  crew: TmdbCastMember[]
}

export interface TmdbAggregateCredits {
  cast: TmdbAggregateCastMember[]
  crew: TmdbAggregateCrewMember[]
}

export interface TmdbKeyword {
  id: number
  name: string
}

export interface TmdbExternalIds {
  imdb_id?: string | null
  tvdb_id?: number | null
  tvrage_id?: number | null
  facebook_id?: string | null
  instagram_id?: string | null
  twitter_id?: string | null
  wikidata_id?: string | null
}

export interface TmdbReleaseDate {
  certification: string
  iso_639_1: string
  note: string
  release_date: string
  type: number
}

export interface TmdbReleaseDatesByCountry {
  iso_3166_1: string
  release_dates: TmdbReleaseDate[]
}

export interface TmdbContentRating {
  iso_3166_1: string
  rating: string
  descriptors?: string[]
}

export interface TmdbReviewAuthor {
  name: string
  username: string
  avatar_path: string | null
  rating: number | null
}

export interface TmdbReview {
  id: string
  author: string
  author_details: TmdbReviewAuthor
  content: string
  created_at: string
  updated_at: string
  url: string
}

export interface TmdbImage {
  aspect_ratio: number
  file_path: string
  height: number
  width: number
  iso_639_1: string | null
  vote_average: number
  vote_count: number
}

export interface TmdbImages {
  backdrops: TmdbImage[]
  posters: TmdbImage[]
  logos: TmdbImage[]
  stills?: TmdbImage[]
  profiles?: TmdbImage[]
}

export interface TmdbAlternativeTitle {
  iso_3166_1: string
  title: string
  type: string
}

export interface TmdbProvider {
  display_priority: number
  logo_path: string | null
  provider_id: number
  provider_name: string
}

export interface TmdbProviderRegion {
  link: string
  flatrate?: TmdbProvider[]
  rent?: TmdbProvider[]
  buy?: TmdbProvider[]
  ads?: TmdbProvider[]
  free?: TmdbProvider[]
}

export interface TmdbWatchProviders {
  results: Record<string, TmdbProviderRegion>
}

export interface TmdbEpisodeGroup {
  id: string
  name: string
  description: string
  episode_count: number
  group_count: number
  network: TmdbNetwork | null
  /** 1 Original air, 2 Absolute, 3 DVD, 4 Digital, 5 Story arc, 6 Production, 7 TV */
  type: number
}

/**
 * Full payload for a single episode group, returned by GET /tv/episode_group/{id}.
 * Each `groups[]` entry is a "season" within the group, containing an
 * ordered list of episodes referencing original (season, episode) pairs.
 */
export interface TmdbEpisodeGroupEntry {
  id: string
  name: string
  order: number
  episodes: Array<{
    id: number
    name: string
    overview: string
    air_date: string | null
    season_number: number
    episode_number: number
    /** Override numbers within this group, sometimes resequenced. */
    order: number
    runtime?: number | null
    still_path?: string | null
  }>
  locked: boolean
}

export interface TmdbEpisodeGroupFull extends TmdbEpisodeGroup {
  groups: TmdbEpisodeGroupEntry[]
}

export interface TmdbMovie {
  id: number
  title: string
  original_title: string
  overview: string
  poster_path: string | null
  backdrop_path: string | null
  release_date: string
  runtime: number
  vote_average: number
  vote_count: number
  popularity?: number
  genres: TmdbGenre[]
  status?: string
  tagline?: string
  budget?: number
  revenue?: number
  homepage?: string
  imdb_id?: string
  original_language?: string
  spoken_languages?: TmdbSpokenLanguage[]
  production_companies?: TmdbProductionCompany[]
  production_countries?: TmdbCountry[]
  belongs_to_collection?: TmdbCollectionRef | null
  adult?: boolean

  // append_to_response fields
  videos?: { results: TmdbVideo[] }
  credits?: TmdbCredits
  keywords?: { keywords: TmdbKeyword[] }
  recommendations?: TmdbPagedResults<TmdbMovie>
  similar?: TmdbPagedResults<TmdbMovie>
  release_dates?: { results: TmdbReleaseDatesByCountry[] }
  external_ids?: TmdbExternalIds
  images?: TmdbImages
  alternative_titles?: { titles: TmdbAlternativeTitle[] }
  reviews?: TmdbPagedResults<TmdbReview>
  'watch/providers'?: TmdbWatchProviders
}

export interface TmdbTvShow {
  id: number
  name: string
  original_name: string
  overview: string
  poster_path: string | null
  backdrop_path: string | null
  first_air_date: string
  last_air_date?: string | null
  vote_average: number
  vote_count: number
  popularity?: number
  number_of_seasons: number
  number_of_episodes?: number
  episode_run_time?: number[]
  genres: TmdbGenre[]
  status?: string
  tagline?: string
  type?: string
  in_production?: boolean
  homepage?: string
  original_language?: string
  origin_country?: string[]
  languages?: string[]
  spoken_languages?: TmdbSpokenLanguage[]
  production_companies?: TmdbProductionCompany[]
  production_countries?: TmdbCountry[]
  networks?: TmdbNetwork[]
  created_by?: TmdbCreator[]
  seasons?: TmdbSeasonSummary[]
  last_episode_to_air?: TmdbEpisode | null
  next_episode_to_air?: TmdbEpisode | null
  adult?: boolean

  // append_to_response fields
  videos?: { results: TmdbVideo[] }
  credits?: TmdbCredits
  aggregate_credits?: TmdbAggregateCredits
  keywords?: { results: TmdbKeyword[] }
  recommendations?: TmdbPagedResults<TmdbTvShow>
  similar?: TmdbPagedResults<TmdbTvShow>
  content_ratings?: { results: TmdbContentRating[] }
  external_ids?: TmdbExternalIds
  images?: TmdbImages
  alternative_titles?: { results: TmdbAlternativeTitle[] }
  reviews?: TmdbPagedResults<TmdbReview>
  'watch/providers'?: TmdbWatchProviders
  episode_groups?: { results: TmdbEpisodeGroup[] }
}

export interface TmdbSeasonSummary {
  id: number
  name: string
  overview: string
  poster_path: string | null
  season_number: number
  episode_count: number
  air_date: string | null
  vote_average?: number
}

export interface TmdbSeason extends TmdbSeasonSummary {
  episodes: TmdbEpisode[]
  // append_to_response
  credits?: TmdbCredits
  aggregate_credits?: TmdbAggregateCredits
  external_ids?: TmdbExternalIds
  images?: TmdbImages
  videos?: { results: TmdbVideo[] }
}

export interface TmdbEpisode {
  id: number
  name: string
  overview: string
  still_path: string | null
  episode_number: number
  season_number: number
  air_date: string | null
  vote_average: number
  vote_count?: number
  runtime?: number | null
  production_code?: string
  show_id?: number
  guest_stars?: TmdbCastMember[]
  crew?: TmdbCastMember[]
  // append_to_response
  credits?: TmdbCredits
  external_ids?: TmdbExternalIds
  images?: TmdbImages
  videos?: { results: TmdbVideo[] }
}

export interface TmdbPerson {
  id: number
  name: string
  also_known_as: string[]
  biography: string
  birthday: string | null
  deathday: string | null
  gender: number
  homepage: string | null
  imdb_id: string | null
  known_for_department: string
  place_of_birth: string | null
  popularity: number
  profile_path: string | null
  adult?: boolean

  // append_to_response
  combined_credits?: TmdbCombinedCredits
  external_ids?: TmdbExternalIds
  images?: TmdbImages
}

export interface TmdbCombinedCreditCast {
  id: number
  credit_id: string
  character: string
  media_type: 'movie' | 'tv'
  title?: string
  name?: string
  poster_path: string | null
  backdrop_path: string | null
  release_date?: string
  first_air_date?: string
  vote_average: number
  popularity?: number
  episode_count?: number
}

export interface TmdbCombinedCreditCrew extends TmdbCombinedCreditCast {
  job: string
  department: string
}

export interface TmdbCombinedCredits {
  cast: TmdbCombinedCreditCast[]
  crew: TmdbCombinedCreditCrew[]
}

export interface TmdbPagedResults<T> {
  page: number
  results: T[]
  total_pages: number
  total_results: number
}

export interface TmdbSearchMultiResult extends TmdbMovie {
  media_type: 'movie' | 'tv' | 'person'
  name?: string
  first_air_date?: string
  profile_path?: string | null
  known_for?: any[]
}

/** TMDB discover result - permissive interface for movie / tv / multi. */
export interface TmdbDiscoverItem {
  id: number
  title?: string
  name?: string
  overview?: string
  poster_path?: string | null
  backdrop_path?: string | null
  release_date?: string
  first_air_date?: string
  vote_average?: number
  vote_count?: number
  adult?: boolean
  original_language?: string
  media_type?: string
}

/* ────────────────────────────────────────────────────────────── */
/*  Fetcher                                                        */
/* ────────────────────────────────────────────────────────────── */

// Build-time default, read from VITE_TMDB_API_KEY in .env. Lets the
// app ship working out of the box; users can still override per-install
// via Settings.
const BUILT_IN_TMDB_KEY = (import.meta.env.VITE_TMDB_API_KEY || '').trim()

const apiKey = () => {
  try {
    const direct = readSecret('jf_tmdb_key')
    if (direct) return direct
    const persisted = readSecret('jf_prefs')
    if (persisted) {
      const parsed = JSON.parse(persisted)
      const key = parsed?.state?.tmdbApiKey
      if (typeof key === 'string' && key) return key
    }
  } catch {
    // ignore
  }
  return BUILT_IN_TMDB_KEY
}

export function hasTmdbKey(): boolean {
  return !!apiKey()
}

export function isUsingBuiltInTmdbKey(): boolean {
  try {
    const direct = readSecret('jf_tmdb_key')
    if (direct) return false
    const persisted = readSecret('jf_prefs')
    if (persisted) {
      const parsed = JSON.parse(persisted)
      const key = parsed?.state?.tmdbApiKey
      if (typeof key === 'string' && key) return false
    }
  } catch { /* ignore */ }
  return !!BUILT_IN_TMDB_KEY
}

async function tmdbFetch<T>(path: string, params: Record<string, string> = {}): Promise<T | null> {
  const key = apiKey()
  if (!key) return null

  const url = new URL(`${BASE_URL}${path}`)
  url.searchParams.set('api_key', key)
  for (const [k, v] of Object.entries(params)) {
    url.searchParams.set(k, v)
  }

  try {
    const res = await fetch(url.toString())
    if (!res.ok) return null
    return res.json() as Promise<T>
  } catch {
    return null
  }
}

export function getTmdbImageUrl(path: string | null | undefined, size: string = 'w500'): string {
  if (!path) return ''
  return `${IMG_BASE}/${size}${path}`
}

/* ────────────────────────────────────────────────────────────── */
/*  Append-to-response combos (one consolidated request each)      */
/* ────────────────────────────────────────────────────────────── */

const MOVIE_APPENDS = [
  'videos',
  'credits',
  'similar',
  'recommendations',
  'keywords',
  'release_dates',
  'external_ids',
  'images',
  'alternative_titles',
  'reviews',
  'watch/providers',
].join(',')

const TV_APPENDS = [
  'videos',
  'credits',
  'aggregate_credits',
  'similar',
  'recommendations',
  'keywords',
  'content_ratings',
  'external_ids',
  'images',
  'alternative_titles',
  'reviews',
  'watch/providers',
  'episode_groups',
].join(',')

const SEASON_APPENDS = ['credits', 'aggregate_credits', 'external_ids', 'images', 'videos'].join(',')
const EPISODE_APPENDS = ['credits', 'external_ids', 'images', 'videos'].join(',')
const PERSON_APPENDS = ['combined_credits', 'external_ids', 'images'].join(',')

export async function getMovieFull(id: number) {
  return tmdbFetch<TmdbMovie>(`/movie/${id}`, {
    append_to_response: MOVIE_APPENDS,
    include_image_language: 'en,null',
  })
}

export async function getTvShowFull(id: number) {
  return tmdbFetch<TmdbTvShow>(`/tv/${id}`, {
    append_to_response: TV_APPENDS,
    include_image_language: 'en,null',
  })
}

export async function getSeasonFull(tvId: number, seasonNum: number) {
  return tmdbFetch<TmdbSeason>(`/tv/${tvId}/season/${seasonNum}`, {
    append_to_response: SEASON_APPENDS,
  })
}

export async function getEpisodeFull(tvId: number, seasonNum: number, episodeNum: number) {
  return tmdbFetch<TmdbEpisode>(`/tv/${tvId}/season/${seasonNum}/episode/${episodeNum}`, {
    append_to_response: EPISODE_APPENDS,
  })
}

export async function getPersonFull(id: number) {
  return tmdbFetch<TmdbPerson>(`/person/${id}`, {
    append_to_response: PERSON_APPENDS,
  })
}

export async function getCollection(id: number) {
  return tmdbFetch<TmdbCollection>(`/collection/${id}`)
}

export async function getEpisodeGroup(id: string) {
  return tmdbFetch<TmdbEpisodeGroupFull>(`/tv/episode_group/${id}`)
}

export async function getTrending(
  mediaType: 'all' | 'movie' | 'tv' | 'person' = 'all',
  window: 'day' | 'week' = 'week',
) {
  return tmdbFetch<TmdbPagedResults<TmdbSearchMultiResult>>(`/trending/${mediaType}/${window}`)
}

export async function searchMulti(query: string) {
  return tmdbFetch<TmdbPagedResults<TmdbSearchMultiResult>>('/search/multi', { query })
}

export async function discoverMovies(params: Record<string, string> = {}) {
  return tmdbFetch<TmdbPagedResults<TmdbMovie>>('/discover/movie', params)
}

export async function discoverTv(params: Record<string, string> = {}) {
  return tmdbFetch<TmdbPagedResults<TmdbTvShow>>('/discover/tv', params)
}

/**
 * Items hitting theatres / streaming releases in the near future. TMDB
 * `/movie/upcoming` is region-aware via the `region` param.
 */
export async function getUpcomingMovies(region?: string, page = 1) {
  return tmdbFetch<TmdbPagedResults<TmdbMovie>>('/movie/upcoming', {
    page: String(page),
    ...(region ? { region } : {}),
  })
}

/**
 * TMDB top-rated lists. The community-vote-driven canon, useful as a
 * "have I missed any of these?" prompt against the user's library.
 */
export async function getTopRatedMovies(page = 1) {
  return tmdbFetch<TmdbPagedResults<TmdbMovie>>('/movie/top_rated', {
    page: String(page),
  })
}

export async function getTopRatedTv(page = 1) {
  return tmdbFetch<TmdbPagedResults<TmdbTvShow>>('/tv/top_rated', {
    page: String(page),
  })
}

/* ────────────────────────────────────────────────────────────── */
/*  Backwards-compatible thin wrappers                             */
/*  Existing call sites still work but everything routes through  */
/*  the consolidated full-payload requests above.                  */
/* ────────────────────────────────────────────────────────────── */

export async function getMovieDetails(id: number) {
  return getMovieFull(id)
}

export async function getMovieCredits(id: number) {
  const m = await getMovieFull(id)
  return m?.credits ?? null
}

export async function getMovieVideos(id: number) {
  const m = await getMovieFull(id)
  return m?.videos ?? null
}

export async function getSimilarMovies(id: number) {
  const m = await getMovieFull(id)
  return m?.similar ?? null
}

export async function getTvShowDetails(id: number) {
  return getTvShowFull(id)
}

export async function getTvShowCredits(id: number) {
  const t = await getTvShowFull(id)
  return t?.credits ?? null
}

export async function getSeasonDetails(tvId: number, seasonNum: number) {
  return getSeasonFull(tvId, seasonNum)
}

/* Search result alias for legacy callers */
export type TmdbSearchResult = TmdbPagedResults<TmdbSearchMultiResult>

/**
 * Pick the best logo from a TMDB images.logos array. We already request
 * `include_image_language=en,null` so the input is pre-filtered to
 * English + no-language variants. Within that set, we prefer English
 * logos (they include the wordmark) then fall back to language-less
 * variants (typically symbol-only marks). Ties broken by vote_average.
 */
export function pickTmdbLogo(logos?: TmdbImage[] | null): TmdbImage | null {
  if (!logos || logos.length === 0) return null
  const sorted = [...logos].sort((a, b) => (b.vote_average || 0) - (a.vote_average || 0))
  return (
    sorted.find(l => l.iso_639_1 === 'en') ||
    sorted.find(l => l.iso_639_1 === null) ||
    sorted[0] ||
    null
  )
}
