import 'server-only';
import { createHash } from 'node:crypto';
import type { IgdbGame } from '../igdb/types';
import { igdbImageUrl } from '../igdb/types';
import type { RawgGame } from '../rawg/client';
import type { GameFacts } from './types';
import type { Overrides } from '../config/overrides';

export function slugify(name: string): string {
  return name
    .toLowerCase()
    .normalize('NFKD')
    .replace(/[\u0300-\u036f]/g, '')
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '')
    .slice(0, 80);
}

function unixToIso(seconds?: number): string | null {
  if (!seconds) return null;
  return new Date(seconds * 1000).toISOString();
}

/**
 * Best-effort age rating label. IGDB's age_ratings.category numeric enum
 * (which organization) and .rating numeric enum (which tier within that
 * organization) both come from a reference table that's occasionally
 * extended — verify against https://api-docs.igdb.com/#age-rating before
 * trusting this for anything compliance-sensitive. For an editorial
 * gaming site this best-effort label is fine; it is NOT a substitute for
 * official rating board data if that ever matters for your use case.
 */
function mapAgeRating(igdbGame: IgdbGame): string | null {
  const first = igdbGame.age_ratings?.[0];
  if (!first) return null;
  const ESRB_MAP: Record<number, string> = {
    1: 'Three',
    2: 'Seven',
    3: 'Twelve',
    4: 'Sixteen',
    5: 'Eighteen',
    6: 'RP',
    7: 'EC',
    8: 'E',
    9: 'E10+',
    10: 'T',
    11: 'M',
    12: 'AO',
  };
  return ESRB_MAP[first.rating] ?? `Rating code ${first.rating}`;
}

export function mapIgdbToFacts(igdbGame: IgdbGame, recentNewsHeadlines: string[] = []): GameFacts {
  return {
    name: igdbGame.name,
    slug: igdbGame.slug || slugify(igdbGame.name),
    releaseDate: unixToIso(igdbGame.first_release_date),
    genres: igdbGame.genres?.map((g) => g.name) ?? [],
    platforms: igdbGame.platforms?.map((p) => p.name) ?? [],
    developers:
      igdbGame.involved_companies?.filter((c) => c.developer).map((c) => c.company.name) ?? [],
    publishers:
      igdbGame.involved_companies?.filter((c) => c.publisher).map((c) => c.company.name) ?? [],
    gameModes: igdbGame.game_modes?.map((m) => m.name) ?? [],
    ageRating: mapAgeRating(igdbGame),
    rating: igdbGame.rating ?? null,
    ratingCount: igdbGame.rating_count ?? null,
    summary: igdbGame.summary ?? null,
    storyline: igdbGame.storyline ?? null,
    coverUrl: igdbGame.cover ? igdbImageUrl(igdbGame.cover.image_id, '1080p') : null,
    screenshots: igdbGame.screenshots?.map((s) => igdbImageUrl(s.image_id, 'screenshot_huge')) ?? [],
    videoIds: igdbGame.videos?.map((v) => v.video_id) ?? [],
    recentNewsHeadlines,
  };
}

/** Fill gaps only — IGDB fields always win when both sources have a value. */
export function mergeRawgEnrichment(facts: GameFacts, rawg: Partial<RawgGame> | null): GameFacts {
  if (!rawg) return facts;
  return {
    ...facts,
    platforms: facts.platforms.length ? facts.platforms : (rawg.platforms?.map((p) => p.platform.name) ?? []),
    genres: facts.genres.length ? facts.genres : (rawg.genres?.map((g) => g.name) ?? []),
    developers: facts.developers.length ? facts.developers : (rawg.developers?.map((d) => d.name) ?? []),
    publishers: facts.publishers.length ? facts.publishers : (rawg.publishers?.map((p) => p.name) ?? []),
    summary: facts.summary ?? rawg.description_raw ?? null,
    ageRating: facts.ageRating ?? rawg.esrb_rating?.name ?? null,
    coverUrl: facts.coverUrl ?? rawg.background_image ?? null,
    releaseDate: facts.releaseDate ?? (rawg.released ? new Date(rawg.released).toISOString() : null),
  };
}

/** Manual corrections from config/overrides.json always win over API data. */
export function applyPinnedFacts(facts: GameFacts, pinned: Overrides['pinnedFacts'][string] | null): GameFacts {
  if (!pinned) return facts;
  return {
    ...facts,
    name: pinned.name ?? facts.name,
    releaseDate: pinned.releaseDate ?? facts.releaseDate,
    summary: pinned.summary ?? facts.summary,
    coverUrl: pinned.coverUrl ?? facts.coverUrl,
  };
}

/**
 * Stable hash of the fields that matter for "has the source data changed
 * enough to warrant regenerating this page". Deliberately excludes
 * high-churn fields like rating_count (which ticks up constantly and
 * would otherwise queue every published game for refresh every night).
 */
export function computeSourceDataHash(facts: GameFacts): string {
  const stable = {
    name: facts.name,
    releaseDate: facts.releaseDate,
    genres: facts.genres,
    platforms: facts.platforms,
    developers: facts.developers,
    publishers: facts.publishers,
    summary: facts.summary,
    storyline: facts.storyline,
    ageRating: facts.ageRating,
  };
  return createHash('sha256').update(JSON.stringify(stable)).digest('hex');
}

/** Round-trips a stored Game row back into GameFacts so generate-content
 * doesn't need to re-hit IGDB for data that's already been discovered. */
export function gameRecordToFacts(
  game: {
    name: string;
    slug: string;
    releaseDate: Date | null;
    genres: string[];
    platforms: string[];
    developers: string[];
    publishers: string[];
    gameModes: string[];
    ageRating: string | null;
    igdbRating: number | null;
    igdbRatingCount: number | null;
    summary: string | null;
    storyline: string | null;
    coverUrl: string | null;
    screenshots: string[];
    videoIds: string[];
  },
  recentNewsHeadlines: string[] = [],
): GameFacts {
  return {
    name: game.name,
    slug: game.slug,
    releaseDate: game.releaseDate ? game.releaseDate.toISOString() : null,
    genres: game.genres,
    platforms: game.platforms,
    developers: game.developers,
    publishers: game.publishers,
    gameModes: game.gameModes,
    ageRating: game.ageRating,
    rating: game.igdbRating,
    ratingCount: game.igdbRatingCount,
    summary: game.summary,
    storyline: game.storyline,
    coverUrl: game.coverUrl,
    screenshots: game.screenshots,
    videoIds: game.videoIds,
    recentNewsHeadlines,
  };
}
