/**
 * Rotten Tomatoes ratings via the same private Algolia search RT uses on
 * their own site. No official API; the agent / api-key here are public
 * values pulled from the RT web client. Best-effort - results matched
 * fuzzily by title + year.
 *
 * Same approach Jellyseerr uses (server/api/rating/rottentomatoes.ts).
 */

const ALGOLIA_BASE = 'https://79frdp12pn-dsn.algolia.net/1/indexes/*/queries'
const ALGOLIA_HEADERS: Record<string, string> = {
  'x-algolia-agent': 'Algolia%20for%20JavaScript%20(4.14.3)%3B%20Browser%20(lite)',
  'x-algolia-api-key': '175588f6e5f8319b27702e4cc4013561',
  'x-algolia-application-id': '79FRDP12PN',
  'Content-Type': 'application/json',
  Accept: 'application/json',
}

interface RTAlgoliaHit {
  emsId: string
  type: string
  title: string
  titles?: string[]
  releaseYear: number
  rating: string
  vanity: string
  aka?: string[]
  rottenTomatoes?: {
    audienceScore: number
    criticsScore: number
    certifiedFresh: boolean
    scoreSentiment: string
  }
}

interface RTAlgoliaResponse {
  results: Array<{ hits: RTAlgoliaHit[]; index: 'content_rt' | 'people_rt' }>
}

export interface RTRating {
  title: string
  url: string
  year: number
  criticsRating: 'Certified Fresh' | 'Fresh' | 'Rotten'
  criticsScore: number
  audienceRating?: 'Upright' | 'Spilled'
  audienceScore?: number
}

const norm = (s: string): string =>
  s.toLowerCase().replace(/[^\p{L}\p{N} ]/gu, '')

// Cheap, dependency-free Jaro similarity. Good enough for the fuzzy match.
function jaro(a: string, b: string): number {
  if (a === b) return 1
  if (!a.length || !b.length) return 0
  const match = Math.max(0, Math.floor(Math.max(a.length, b.length) / 2) - 1)
  const am = new Array(a.length).fill(false)
  const bm = new Array(b.length).fill(false)
  let m = 0
  for (let i = 0; i < a.length; i++) {
    const lo = Math.max(0, i - match)
    const hi = Math.min(b.length, i + match + 1)
    for (let j = lo; j < hi; j++) {
      if (bm[j] || a[i] !== b[j]) continue
      am[i] = true
      bm[j] = true
      m++
      break
    }
  }
  if (!m) return 0
  let t = 0
  let k = 0
  for (let i = 0; i < a.length; i++) {
    if (!am[i]) continue
    while (!bm[k]) k++
    if (a[i] !== b[k]) t++
    k++
  }
  t = t / 2
  return (m / a.length + m / b.length + (m - t) / m) / 3
}

const INEXACT = 0.25
const ALT = 0.8
const PER_YEAR = 0.4
const MIN = 0.175

const tScore = (h: RTAlgoliaHit, name: string): number => {
  const target = norm(name)
  const f = (t: string, idx: number) => {
    const n = norm(t)
    const sim = n === target ? 1 : jaro(n, target) * INEXACT
    return sim * (idx ? ALT : 1)
  }
  return Math.max(...[h.title, ...(h.aka || []), ...(h.titles || [])].map(f))
}

const yScore = (h: RTAlgoliaHit, year?: number): number =>
  year ? Math.max(0, 1 - Math.abs(h.releaseYear - year) * PER_YEAR) : 1

const extra = (h: RTAlgoliaHit): number => (h.rottenTomatoes ? 1 : 0.5)

const score = (h: RTAlgoliaHit, name: string, year?: number): number =>
  tScore(h, name) * yScore(h, year) * extra(h)

const best = (hits: RTAlgoliaHit[], name: string, year?: number): RTAlgoliaHit | undefined =>
  hits
    .map(h => ({ s: score(h, name, year), h }))
    .filter(({ s }) => s > MIN)
    .sort((a, b) => b.s - a.s)[0]?.h

async function searchRT(name: string, kind: 'movie' | 'tv'): Promise<RTAlgoliaHit[]> {
  const query = kind === 'movie' ? name.replace(/\bthe\b ?/gi, '') : name
  const filters = encodeURIComponent(`isEmsSearchable=1 AND type:"${kind}"`)
  try {
    const res = await fetch(ALGOLIA_BASE, {
      method: 'POST',
      headers: ALGOLIA_HEADERS,
      body: JSON.stringify({
        requests: [
          {
            indexName: 'content_rt',
            query,
            params: `filters=${filters}&hitsPerPage=20`,
          },
        ],
      }),
    })
    if (!res.ok) return []
    const data = (await res.json()) as RTAlgoliaResponse
    return data.results.find(r => r.index === 'content_rt')?.hits || []
  } catch {
    return []
  }
}

function buildRating(
  hit: RTAlgoliaHit,
  kind: 'movie' | 'tv',
): RTRating | null {
  if (!hit?.rottenTomatoes) return null
  const rt = hit.rottenTomatoes
  return {
    title: hit.title,
    year: Number(hit.releaseYear),
    url: `https://www.rottentomatoes.com/${kind === 'movie' ? 'm' : 'tv'}/${hit.vanity}`,
    criticsRating: rt.certifiedFresh
      ? 'Certified Fresh'
      : rt.criticsScore >= 60
        ? 'Fresh'
        : 'Rotten',
    criticsScore: rt.criticsScore,
    audienceRating: rt.audienceScore >= 60 ? 'Upright' : 'Spilled',
    audienceScore: rt.audienceScore,
  }
}

export async function getMovieRating(name: string, year?: number): Promise<RTRating | null> {
  if (!name) return null
  const hits = await searchRT(name, 'movie')
  const hit = best(hits, name, year)
  return hit ? buildRating(hit, 'movie') : null
}

export async function getTvRating(name: string, year?: number): Promise<RTRating | null> {
  if (!name) return null
  const hits = await searchRT(name, 'tv')
  const hit = best(hits, name, year)
  return hit ? buildRating(hit, 'tv') : null
}
