import "server-only";

import type { Game as PrismaGame, Category as PrismaCategory } from "@prisma/client";
import type { GameSummary } from "./site";
import { db } from "./db";

export type SortKey = "newest" | "az";
export const SORT_KEYS: readonly SortKey[] = ["newest", "az"] as const;

export function parseSort(value: unknown): SortKey {
  return value === "az" ? "az" : "newest";
}

export interface PublicGame extends GameSummary {
  officialUrl: string;
  sourceDomain: string;
  embedAllowed: boolean;
  instructions: string;
  controls: string;
  categoryName: string;
  updatedAt: Date;
  createdAt: Date;
}

type GameWithCategory = PrismaGame & { category: PrismaCategory };

function toPublicGame(g: GameWithCategory): PublicGame {
  return {
    id: String(g.id),
    slug: g.slug,
    title: g.title,
    description: g.description,
    categorySlug: g.category.slug,
    categoryName: g.category.name,
    thumbnail: g.thumbnail,
    developer: g.developer,
    publisher: g.publisher,
    officialUrl: g.officialUrl,
    sourceDomain: g.sourceDomain,
    embedAllowed: g.embedAllowed,
    status: g.status as GameSummary["status"],
    featured: g.featured,
    trending: g.trending,
    instructions: g.instructions,
    controls: g.controls,
    createdAt: g.createdAt,
    updatedAt: g.updatedAt,
  };
}

export interface ListGamesOptions {
  q?: string;
  categorySlug?: string;
  sort?: SortKey;
  page?: number;
  pageSize?: number;
}

export interface ListGamesResult {
  games: PublicGame[];
  total: number;
  page: number;
  pageSize: number;
  totalPages: number;
}

const DEFAULT_PAGE_SIZE = 12;

/**
 * Public catalog query. Only `published` games are ever returned.
 * No popularity metrics exist, so sorting is newest / alphabetical only.
 */
export async function listGames(
  options: ListGamesOptions = {},
): Promise<ListGamesResult> {
  const q = options.q?.trim() ?? "";
  const sort: SortKey = options.sort ?? "newest";
  const page = Math.max(1, Math.floor(options.page ?? 1));
  const pageSize = Math.min(
    48,
    Math.max(1, Math.floor(options.pageSize ?? DEFAULT_PAGE_SIZE)),
  );

  const where = {
    status: "published",
    ...(options.categorySlug
      ? { category: { slug: options.categorySlug } }
      : {}),
    ...(q
      ? {
          OR: [
            { title: { contains: q } },
            { description: { contains: q } },
            { developer: { contains: q } },
            { publisher: { contains: q } },
            { category: { name: { contains: q } } },
          ],
        }
      : {}),
  };

  const orderBy =
    sort === "az" ? { title: "asc" as const } : { createdAt: "desc" as const };

  const [total, rows] = await Promise.all([
    db.game.count({ where }),
    db.game.findMany({
      where,
      orderBy,
      include: { category: true },
      skip: (page - 1) * pageSize,
      take: pageSize,
    }),
  ]);

  return {
    games: rows.map(toPublicGame),
    total,
    page,
    pageSize,
    totalPages: Math.max(1, Math.ceil(total / pageSize)),
  };
}

/** Single published game by slug, or null (→ 404, including drafts/disabled). */
export async function getGameBySlug(slug: string): Promise<PublicGame | null> {
  const game = await db.game.findFirst({
    where: { slug, status: "published" },
    include: { category: true },
  });
  return game ? toPublicGame(game) : null;
}

export interface PlayableGame extends PublicGame {
  embedUrl: string | null;
}

/**
 * Player query: like getGameBySlug plus the raw embed URL.
 * The play route runs the embed allowlist check before rendering anything.
 */
export async function getPlayableGame(slug: string): Promise<PlayableGame | null> {
  const game = await db.game.findFirst({
    where: { slug, status: "published" },
    include: { category: true },
  });
  if (!game) return null;
  return { ...toPublicGame(game), embedUrl: game.embedUrl };
}

/** Related published games: same category first, then fill with newest. */
export async function getRelatedGames(
  game: PublicGame,
  limit = 4,
): Promise<PublicGame[]> {
  const sameCategory = await db.game.findMany({
    where: {
      status: "published",
      category: { slug: game.categorySlug },
      slug: { not: game.slug },
    },
    orderBy: { createdAt: "desc" },
    include: { category: true },
    take: limit,
  });
  if (sameCategory.length >= limit) {
    return sameCategory.map(toPublicGame);
  }
  const filler = await db.game.findMany({
    where: {
      status: "published",
      slug: { not: game.slug },
      category: { slug: { not: game.categorySlug } },
    },
    orderBy: { createdAt: "desc" },
    include: { category: true },
    take: limit - sameCategory.length,
  });
  return [...sameCategory, ...filler].map(toPublicGame);
}

export interface HomeSections {
  featured: PublicGame[];
  trending: PublicGame[];
  newest: PublicGame[];
  recentlyAdded: PublicGame[];
}

/** Curated home sections. All queries are published-only and paginated. */
export async function getHomeSections(): Promise<HomeSections> {
  const include = { category: true };
  const [featured, trending, newest, recentlyAdded] = await Promise.all([
    db.game.findMany({
      where: { status: "published", featured: true },
      orderBy: { createdAt: "desc" },
      include,
      take: 4,
    }),
    db.game.findMany({
      where: { status: "published", trending: true },
      orderBy: { createdAt: "desc" },
      include,
      take: 4,
    }),
    db.game.findMany({
      where: { status: "published" },
      orderBy: { createdAt: "desc" },
      include,
      take: 8,
    }),
    db.game.findMany({
      where: { status: "published" },
      orderBy: { updatedAt: "desc" },
      include,
      take: 4,
    }),
  ]);
  return {
    featured: featured.map(toPublicGame),
    trending: trending.map(toPublicGame),
    newest: newest.map(toPublicGame),
    recentlyAdded: recentlyAdded.map(toPublicGame),
  };
}

export interface CategoryWithCount {
  slug: string;
  name: string;
  blurb: string;
  gameCount: number;
}

/** All categories with their published game counts. */
export async function getCategoriesWithCounts(): Promise<CategoryWithCount[]> {
  const categories = await db.category.findMany({
    orderBy: { name: "asc" },
    include: {
      _count: { select: { games: { where: { status: "published" } } } },
    },
  });
  return categories.map((c) => ({
    slug: c.slug,
    name: c.name,
    blurb: c.blurb,
    gameCount: c._count.games,
  }));
}

/** Dashboard-style counts (also reused by the Phase 5 admin panel). */
export async function getCatalogStats() {
  const [total, published, draft, disabled, categories] = await Promise.all([
    db.game.count(),
    db.game.count({ where: { status: "published" } }),
    db.game.count({ where: { status: "draft" } }),
    db.game.count({ where: { status: "disabled" } }),
    db.category.count(),
  ]);
  return { total, published, draft, disabled, categories };
}

/* ---------------- Admin queries (every status, gated by layout) ---------------- */

export interface AdminGame extends PublicGame {
  numericId: number;
  categoryId: number;
  thumbnail: string | null;
  embedUrl: string | null;
}

function toAdminGame(g: GameWithCategory): AdminGame {
  return {
    ...toPublicGame(g),
    numericId: g.id,
    categoryId: g.categoryId,
    thumbnail: g.thumbnail,
    embedUrl: g.embedUrl,
  };
}

export interface AdminListOptions {
  status?: string;
  q?: string;
  page?: number;
  pageSize?: number;
}

/** Full catalog for the admin list, any status, most recently updated first. */
export async function listAllGamesAdmin(options: AdminListOptions = {}) {
  const q = options.q?.trim() ?? "";
  const page = Math.max(1, Math.floor(options.page ?? 1));
  const pageSize = Math.min(
    50,
    Math.max(1, Math.floor(options.pageSize ?? 20)),
  );
  const where = {
    ...(options.status ? { status: options.status } : {}),
    ...(q
      ? {
          OR: [
            { title: { contains: q } },
            { slug: { contains: q } },
            { developer: { contains: q } },
          ],
        }
      : {}),
  };
  const [total, rows] = await Promise.all([
    db.game.count({ where }),
    db.game.findMany({
      where,
      orderBy: { updatedAt: "desc" },
      include: { category: true },
      skip: (page - 1) * pageSize,
      take: pageSize,
    }),
  ]);
  return {
    games: rows.map(toAdminGame),
    total,
    page,
    pageSize,
    totalPages: Math.max(1, Math.ceil(total / pageSize)),
  };
}

export async function getGameAdmin(id: number): Promise<AdminGame | null> {
  if (!Number.isInteger(id) || id <= 0) return null;
  const game = await db.game.findUnique({
    where: { id },
    include: { category: true },
  });
  return game ? toAdminGame(game) : null;
}

export async function getRecentGamesAdmin(limit = 5): Promise<AdminGame[]> {
  const rows = await db.game.findMany({
    orderBy: { createdAt: "desc" },
    include: { category: true },
    take: Math.min(20, Math.max(1, limit)),
  });
  return rows.map(toAdminGame);
}
