import type {
  CommunityClanRanking,
  CommunityPlayerRanking,
  CommunityServer,
  CommunitySnapshot,
} from "../shared/contracts.js";

const MAX_RESPONSE_BYTES = 256 * 1024;

function record(value: unknown, at: string): Record<string, unknown> {
  if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`${at} est invalide`);
  return value as Record<string, unknown>;
}

function boundedText(value: unknown, at: string, maximum: number): string {
  if (typeof value !== "string") throw new Error(`${at} est invalide`);
  const clean = value.trim();
  if (clean.length === 0 || clean.length > maximum) throw new Error(`${at} est invalide`);
  return clean;
}

function count(value: unknown, at: string): number {
  if (!Number.isSafeInteger(value) || (value as number) < 0) throw new Error(`${at} est invalide`);
  return value as number;
}

function list<T>(value: unknown, at: string, maximum: number, read: (item: unknown, index: number) => T): T[] {
  if (!Array.isArray(value) || value.length > maximum) throw new Error(`${at} est invalide`);
  return value.map(read);
}

function player(item: unknown, index: number): CommunityPlayerRanking {
  const value = record(item, `players[${index}]`);
  const platform = boundedText(value.platform, `players[${index}].platform`, 7);
  if (platform !== "JAVA" && platform !== "BEDROCK") throw new Error(`players[${index}].platform est invalide`);
  return {
    name: boundedText(value.name, `players[${index}].name`, 32),
    platform,
    value: count(value.value, `players[${index}].value`),
    secondary: count(value.secondary, `players[${index}].secondary`),
  };
}

function clan(item: unknown, index: number): CommunityClanRanking {
  const value = record(item, `clans[${index}]`);
  return {
    name: boundedText(value.name, `clans[${index}].name`, 48),
    tag: boundedText(value.tag, `clans[${index}].tag`, 8),
    level: count(value.level, `clans[${index}].level`),
    members: count(value.members, `clans[${index}].members`),
    claims: count(value.claims, `clans[${index}].claims`),
    bankBalance: count(value.bankBalance, `clans[${index}].bankBalance`),
    power: count(value.power, `clans[${index}].power`),
  };
}

function server(item: unknown, index: number): CommunityServer {
  const value = record(item, `servers[${index}]`);
  return {
    id: boundedText(value.id, `servers[${index}].id`, 32),
    label: boundedText(value.label, `servers[${index}].label`, 48),
    players: count(value.players, `servers[${index}].players`),
  };
}

export function unavailableCommunity(): CommunitySnapshot {
  return {
    state: "unavailable",
    updatedAt: null,
    playersOnline: null,
    servers: [],
    fortunes: [],
    playtime: [],
    clans: [],
    pvpAvailable: false,
  };
}

export function parseCommunitySnapshot(raw: unknown): CommunitySnapshot {
  const value = record(raw, "community");
  if (value.schemaVersion !== 1) throw new Error("community.schemaVersion est invalide");
  const updatedAt = boundedText(value.updatedAt, "community.updatedAt", 48);
  if (!Number.isFinite(Date.parse(updatedAt))) throw new Error("community.updatedAt est invalide");
  return {
    state: "live",
    updatedAt,
    playersOnline: count(value.playersOnline, "community.playersOnline"),
    servers: list(value.servers, "community.servers", 8, server),
    fortunes: list(value.fortunes, "community.fortunes", 20, player),
    playtime: list(value.playtime, "community.playtime", 20, player),
    clans: list(value.clans, "community.clans", 20, clan),
    pvpAvailable: value.pvpAvailable === true,
  };
}

export async function fetchCommunitySnapshot(url: string, signal?: AbortSignal): Promise<CommunitySnapshot> {
  const response = await fetch(url, {
    signal,
    redirect: "error",
    headers: { Accept: "application/json" },
  });
  if (!response.ok) throw new Error(`Flux communautaire indisponible (${response.status})`);
  const announced = Number(response.headers.get("content-length") ?? 0);
  if (announced > MAX_RESPONSE_BYTES) throw new Error("Flux communautaire trop volumineux");
  const bytes = new Uint8Array(await response.arrayBuffer());
  if (bytes.byteLength > MAX_RESPONSE_BYTES) throw new Error("Flux communautaire trop volumineux");
  return parseCommunitySnapshot(JSON.parse(new TextDecoder().decode(bytes)) as unknown);
}
