import {
  genreOverlapCount,
  posterFingerprintSimilarity,
  sharedNameOverlapCount,
  titlePhoneticSimilarity,
  titleSimilarity,
} from './movie-matcher.ts';
import { extractYear, isNonTheatricalTitle, isRecentTheatricalYear } from './title-classification.ts';

const IMDB_ID_RE = /^tt\d{5,}$/i;

export interface TmdbSearchResult {
  id: number;
  title: string;
  release_date?: string;
  poster_path?: string | null;
  backdrop_path?: string | null;
  overview?: string;
  popularity?: number;
  vote_average?: number;
  vote_count?: number;
}

export interface TmdbDetails {
  id: number;
  title?: string;
  original_title?: string;
  original_language?: string;
  imdb_id?: string | null;
  runtime?: number | null;
  genres?: Array<{ id: number; name: string }>;
  spoken_languages?: Array<{
    iso_639_1?: string;
    english_name?: string;
    name?: string;
  }>;
  overview?: string;
  release_date?: string;
  poster_path?: string | null;
  backdrop_path?: string | null;
  popularity?: number;
  vote_average?: number;
  vote_count?: number;
  videos?: {
    results?: TmdbVideo[];
  };
}

export interface TmdbCredits {
  crew: Array<{
    id?: number;
    job: string;
    name: string;
    department?: string;
    popularity?: number;
    profile_path?: string | null;
  }>;
  cast: Array<{
    id?: number;
    name: string;
    order: number;
    character?: string;
    known_for_department?: string;
    popularity?: number;
    profile_path?: string | null;
  }>;
}

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

export interface TitleRow {
  id: string;
  title_en: string;
  title_ar?: string | null;
  title_original?: string | null;
  language?: string | null;
  imdb_id?: string | null;
  release_date?: string | null;
  duration_min?: number | null;
  genre_raw?: string | null;
  poster_url?: string | null;
  cast_list?: string | null;
  tmdb_id?: string | null;
}

function normalizeImdbId(value: string | null | undefined): string | null {
  if (!value) return null;
  const token = value.trim().toLowerCase();
  return IMDB_ID_RE.test(token) ? token : null;
}

export function collectTitleVariants(...values: Array<string | null | undefined>): string[] {
  const out: string[] = [];
  const seen = new Set<string>();
  for (const value of values) {
    const trimmed = value?.trim();
    if (!trimmed) continue;
    const key = trimmed.toLowerCase();
    if (seen.has(key)) continue;
    seen.add(key);
    out.push(trimmed);
  }
  return out;
}

function scoreTmdbTitleEvidence(
  title: TitleRow,
  candidateTitles: Array<string | null | undefined>,
): { similarity: number; phonetic: number; score: number } {
  const sourceVariants = collectTitleVariants(title.title_en, title.title_original, title.title_ar);
  const targetVariants = collectTitleVariants(...candidateTitles);
  let similarity = 0;
  let phonetic = 0;

  for (const sourceVariant of sourceVariants) {
    for (const targetVariant of targetVariants) {
      similarity = Math.max(similarity, titleSimilarity(sourceVariant, targetVariant));
      phonetic = Math.max(phonetic, titlePhoneticSimilarity(sourceVariant, targetVariant));
    }
  }

  return {
    similarity,
    phonetic,
    score: similarity * 0.84 + phonetic * 0.16,
  };
}

export function pickBestSearchResult(
  title: TitleRow,
  results: TmdbSearchResult[],
): { result: TmdbSearchResult; score: number } | null {
  const sourceYear = extractYear(title.release_date);
  const scored = results
    .map((result) => {
      const titleEvidence = scoreTmdbTitleEvidence(title, [result.title]);
      const releaseYear = extractYear(result.release_date);
      let score = titleEvidence.score;

      if (sourceYear) {
        if (!releaseYear) return null;
        const diff = Math.abs(releaseYear - sourceYear);
        if (diff > 1) return null;
        if (diff === 0) {
          score += 0.05;
        } else if (titleEvidence.score >= 0.93) {
          score += 0.02;
        } else {
          return null;
        }
      } else if (!isRecentTheatricalYear(releaseYear)) {
        return null;
      }

      if (title.poster_url && !result.poster_path && titleEvidence.score < 0.92) {
        return null;
      }

      const posterSimilarity = posterFingerprintSimilarity(title.poster_url ?? null, result.poster_path ?? null);
      if (posterSimilarity !== null && posterSimilarity >= 0.9) {
        score += 0.02;
      }

      return {
        result,
        score,
        similarity: titleEvidence.similarity,
        phonetic: titleEvidence.phonetic,
        releaseYear,
      };
    })
    .filter((entry): entry is { result: TmdbSearchResult; score: number; similarity: number; phonetic: number; releaseYear: number | null } => {
      if (!entry) return false;
      const { result, similarity, phonetic, score, releaseYear } = entry;
      if (!result.title || isLikelyNonTheatricalSearchResult(result)) return false;
      if (similarity < 0.82 && phonetic < 0.88) return false;
      if (sourceYear && !releaseYear) return false;
      return score >= 0.86;
    })
    .sort((a, b) =>
      b.score - a.score ||
      b.similarity - a.similarity ||
      (b.result.popularity ?? 0) - (a.result.popularity ?? 0) ||
      (b.result.vote_count ?? 0) - (a.result.vote_count ?? 0),
    );

  const best = scored[0];
  const runnerUp = scored[1];
  if (
    best &&
    runnerUp &&
    best.score - runnerUp.score < 0.02 &&
    best.releaseYear === runnerUp.releaseYear
  ) {
    return null;
  }

  return best ? { result: best.result, score: best.score } : null;
}

function isLikelyNonTheatricalSearchResult(result: TmdbSearchResult): boolean {
  const titleLooksNonTheatrical = isNonTheatricalTitle(result.title);
  if (!titleLooksNonTheatrical) return false;
  return (result.vote_count ?? 0) < 150 && (result.popularity ?? 0) < 30;
}

export function confirmTmdbMatch(
  title: TitleRow,
  searchResult: TmdbSearchResult | null,
  detailsEn: TmdbDetails,
  detailsAr: TmdbDetails,
  credits: TmdbCredits,
): { accepted: boolean; reason: string } {
  const sourceImdbId = normalizeImdbId(title.imdb_id);
  const tmdbImdbId = normalizeImdbId(detailsEn.imdb_id);
  if (sourceImdbId && tmdbImdbId) {
    return sourceImdbId === tmdbImdbId
      ? { accepted: true, reason: 'matched by imdb id' }
      : { accepted: false, reason: 'imdb mismatch' };
  }

  const titleEvidence = scoreTmdbTitleEvidence(title, [
    detailsEn.title,
    detailsEn.original_title,
    detailsAr.title,
    searchResult?.title,
  ]);
  if (titleEvidence.similarity < 0.82 && titleEvidence.phonetic < 0.88) {
    return { accepted: false, reason: 'weak title similarity' };
  }

  let score = titleEvidence.score;
  let corroboratingSignals = 0;

  const sourceYear = extractYear(title.release_date);
  const matchedYear = extractYear(detailsEn.release_date || searchResult?.release_date || null);
  if (sourceYear && matchedYear) {
    const diff = Math.abs(sourceYear - matchedYear);
    if (diff > 1) return { accepted: false, reason: 'release year mismatch' };
    if (diff === 0) {
      score += 0.06;
      corroboratingSignals += 1;
    } else if (titleEvidence.score >= 0.93) {
      score += 0.02;
      corroboratingSignals += 1;
    } else {
      return { accepted: false, reason: 'release year too far for weak title match' };
    }
  }

  const matchedRuntime = detailsEn.runtime ?? detailsAr.runtime ?? null;
  if (Number.isFinite(title.duration_min) && Number.isFinite(matchedRuntime) && title.duration_min && matchedRuntime) {
    const runtimeDiff = Math.abs(title.duration_min - matchedRuntime);
    if (runtimeDiff > 35) return { accepted: false, reason: 'runtime mismatch' };
    if (runtimeDiff <= 5) {
      score += 0.08;
      corroboratingSignals += 1;
    } else if (runtimeDiff <= 12) {
      score += 0.04;
      corroboratingSignals += 1;
    } else if (runtimeDiff > 20) {
      score -= 0.06;
    }
  }

  const matchedGenres = (detailsEn.genres ?? detailsAr.genres ?? []).map((genre) => genre.name);
  if ((title.genre_raw ?? '').trim() && matchedGenres.length > 0) {
    if (genreOverlapCount(title.genre_raw ?? null, matchedGenres) > 0) {
      score += 0.04;
      corroboratingSignals += 1;
    } else {
      score -= 0.06;
    }
  }

  const posterSimilarity = posterFingerprintSimilarity(
    title.poster_url ?? null,
    detailsEn.poster_path || searchResult?.poster_path || null,
  );
  if (posterSimilarity !== null && posterSimilarity >= 0.9) {
    score += 0.02;
    corroboratingSignals += 1;
  }

  const castOverlap = sharedNameOverlapCount(
    title.cast_list ?? null,
    credits.cast.slice(0, 8).map((person) => person.name),
  );
  if (castOverlap > 0) {
    score += Math.min(0.06, castOverlap * 0.03);
    corroboratingSignals += 1;
  }

  score = Math.max(0, Math.min(1, score));
  const sourceSignalCount = [
    title.release_date,
    title.duration_min,
    title.genre_raw,
    title.poster_url,
    title.cast_list,
    title.imdb_id,
  ].filter(Boolean).length;
  const minimumScore = sourceSignalCount >= 2 ? 0.86 : 0.9;

  if (score < minimumScore) {
    return { accepted: false, reason: `composite score too low (${score.toFixed(2)})` };
  }
  if (sourceSignalCount >= 2 && corroboratingSignals === 0 && titleEvidence.score < 0.93) {
    return { accepted: false, reason: 'missing corroborating metadata signals' };
  }

  return { accepted: true, reason: `confirmed (${score.toFixed(2)})` };
}
