import "server-only";

import { getHostname, isSafeHttpUrl } from "./embeds";

export const GAME_STATUSES = ["draft", "published", "disabled"] as const;
export type GameStatusInput = (typeof GAME_STATUSES)[number];

export function isGameStatus(value: unknown): value is GameStatusInput {
  return (
    typeof value === "string" &&
    (GAME_STATUSES as readonly string[]).includes(value)
  );
}

export interface GameFormData {
  title: string;
  slug: string;
  description: string;
  categoryId: number;
  developer: string;
  publisher: string;
  officialUrl: string;
  thumbnail: string;
  embedAllowed: boolean;
  embedUrl: string;
  status: GameStatusInput;
  featured: boolean;
  trending: boolean;
  instructions: string;
  controls: string;
}

export type GameFieldErrors = Partial<Record<keyof GameFormData, string>>;

const SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
const MAX = {
  title: 120,
  slug: 80,
  description: 2000,
  name: 120,
  url: 2048,
  instructions: 5000,
  controls: 2000,
} as const;

/** "my Cool Game!" → "my-cool-game" */
export function slugify(input: string): string {
  return input
    .toLowerCase()
    .normalize("NFKD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "")
    .slice(0, MAX.slug);
}

/** HTTPS URL for official sources. Relative paths never allowed here. */
function officialUrlError(url: string): string | null {
  if (!url) return "Official URL is required.";
  if (url.length > MAX.url) return "URL is too long.";
  let parsed: URL;
  try {
    parsed = new URL(url);
  } catch {
    return "Enter a valid absolute URL (https://…).";
  }
  if (parsed.protocol === "javascript:" || parsed.protocol === "data:") {
    return "That URL scheme is not allowed.";
  }
  if (parsed.protocol !== "https:") {
    return "Official URL must use HTTPS.";
  }
  return null;
}

/** Thumbnails may be absolute HTTPS URLs or site-relative paths (/img/…). */
function thumbnailError(value: string): string | null {
  if (!value) return null;
  if (value.length > MAX.url) return "Thumbnail URL is too long.";
  if (value.startsWith("/")) {
    return /^[A-Za-z0-9/_\-.]+$/.test(value)
      ? null
      : "Relative thumbnail paths may only contain letters, numbers, /, _, - and dots.";
  }
  let parsed: URL;
  try {
    parsed = new URL(value);
  } catch {
    return "Thumbnail must be an HTTPS URL or a /-relative path.";
  }
  if (parsed.protocol !== "https:") return "Thumbnail URL must use HTTPS.";
  return null;
}

/** Embed URLs must additionally sit on the configured allowlist. */
function embedUrlError(url: string, allowlist: string[]): string | null {
  if (!url) return "Embed URL is required when embedding is allowed.";
  if (!isSafeHttpUrl(url)) {
    return "Embed URL must be a valid HTTPS URL (no javascript:/data:).";
  }
  const host = getHostname(url);
  const ok =
    host !== null &&
    allowlist.some((d) => host === d || host.endsWith(`.${d}`));
  if (!ok) {
    return allowlist.length === 0
      ? "No embed domains are allowlisted (ALLOWED_EMBED_DOMAINS is empty)."
      : "This embed host is not on the allowlist (ALLOWED_EMBED_DOMAINS).";
  }
  return null;
}

export interface ValidatedGame {
  data: GameFormData;
  errors: GameFieldErrors;
}

/**
 * Server-side validation for admin game input. Never trust client checks.
 * `categoryExists` and `allowlist` are injected so this stays DB/env-free
 * and unit-testable.
 */
export function validateGameInput(
  raw: Record<string, unknown>,
  options: { categoryExists: (id: number) => boolean; allowlist: string[] },
): ValidatedGame {
  const str = (v: unknown) =>
    typeof v === "string" ? v.trim() : "";
  const bool = (v: unknown) => v === true || v === "on" || v === "true";

  const title = str(raw.title);
  let slug = str(raw.slug).toLowerCase();
  if (!slug && title) slug = slugify(title);
  const description = str(raw.description);
  const categoryId = Number(raw.categoryId);
  const developer = str(raw.developer);
  const publisher = str(raw.publisher);
  const officialUrl = str(raw.officialUrl);
  const thumbnail = str(raw.thumbnail);
  const embedAllowed = bool(raw.embedAllowed);
  const embedUrl = str(raw.embedUrl);
  const statusRaw = str(raw.status) || "draft";
  const featured = bool(raw.featured);
  const trending = bool(raw.trending);
  const instructions = str(raw.instructions);
  const controls = str(raw.controls);

  const errors: GameFieldErrors = {};

  if (!title) errors.title = "Title is required.";
  else if (title.length > MAX.title)
    errors.title = `Title must be ${MAX.title} characters or fewer.`;

  if (!slug) errors.slug = "Slug is required (or leave blank to auto-generate from the title).";
  else if (slug.length > MAX.slug) errors.slug = "Slug is too long.";
  else if (!SLUG_RE.test(slug))
    errors.slug = "Slug may only contain lowercase letters, numbers and hyphens.";

  if (!description) errors.description = "Description is required.";
  else if (description.length > MAX.description)
    errors.description = `Description must be ${MAX.description} characters or fewer.`;

  if (!Number.isInteger(categoryId) || categoryId <= 0)
    errors.categoryId = "Choose a valid category.";
  else if (!options.categoryExists(categoryId))
    errors.categoryId = "That category does not exist.";

  if (developer.length > MAX.name)
    errors.developer = "Developer name is too long.";
  if (publisher.length > MAX.name)
    errors.publisher = "Publisher name is too long.";

  const urlErr = officialUrlError(officialUrl);
  if (urlErr) errors.officialUrl = urlErr;

  const thumbErr = thumbnailError(thumbnail);
  if (thumbErr) errors.thumbnail = thumbErr;

  if (embedAllowed) {
    const embErr = embedUrlError(embedUrl, options.allowlist);
    if (embErr) errors.embedUrl = embErr;
  }

  if (!isGameStatus(statusRaw)) errors.status = "Invalid status.";

  if (instructions.length > MAX.instructions)
    errors.instructions = "Instructions are too long.";
  if (controls.length > MAX.controls) errors.controls = "Controls text is too long.";

  return {
    data: {
      title,
      slug,
      description,
      categoryId,
      developer,
      publisher,
      officialUrl,
      thumbnail,
      embedAllowed,
      embedUrl: embedAllowed ? embedUrl : "",
      status: isGameStatus(statusRaw) ? statusRaw : "draft",
      featured,
      trending,
      instructions,
      controls,
    },
    errors,
  };
}

export interface CategoryFormData {
  name: string;
  slug: string;
  blurb: string;
}

export function validateCategoryInput(
  raw: Record<string, unknown>,
): { data: CategoryFormData; errors: Partial<Record<keyof CategoryFormData, string>> } {
  const str = (v: unknown) => (typeof v === "string" ? v.trim() : "");
  const name = str(raw.name);
  let slug = str(raw.slug).toLowerCase();
  if (!slug && name) slug = slugify(name);
  const blurb = str(raw.blurb);

  const errors: Partial<Record<keyof CategoryFormData, string>> = {};
  if (!name) errors.name = "Name is required.";
  else if (name.length > 60) errors.name = "Name is too long.";
  if (!slug) errors.slug = "Slug is required.";
  else if (!SLUG_RE.test(slug)) errors.slug = "Slug may only contain lowercase letters, numbers and hyphens.";
  if (blurb.length > 300) errors.blurb = "Blurb is too long.";

  return { data: { name, slug, blurb }, errors };
}

/** Host portion of an official URL, for the sourceDomain column. */
export function sourceDomainOf(url: string): string {
  return getHostname(url) ?? "";
}
