#!/usr/bin/env node
import fs from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const baseUrl = (process.env.PRE_RACE_BASE || process.env.SMOKE_BASE || "http://127.0.0.1:4000").replace(/\/+$/, "");
const failures = [];
const warnings = [];

function fail(message) {
  failures.push(message);
}

function warn(message) {
  warnings.push(message);
}

async function getJson(endpoint) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 5000);
  try {
    const response = await fetch(`${baseUrl}${endpoint}`, { signal: controller.signal, cache: "no-store" });
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return await response.json();
  } finally {
    clearTimeout(timeout);
  }
}

async function getText(endpoint) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 5000);
  try {
    const response = await fetch(`${baseUrl}${endpoint}`, { signal: controller.signal, cache: "no-store" });
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return await response.text();
  } finally {
    clearTimeout(timeout);
  }
}

async function checkFile(relativePath) {
  try {
    await fs.access(path.join(root, relativePath));
  } catch (_err) {
    fail(`Missing package file: ${relativePath}`);
  }
}

async function checkEndpoint(endpoint, label) {
  try {
    const text = await getText(endpoint);
    if (!text.trim()) fail(`${label} returned an empty response.`);
  } catch (err) {
    fail(`${label} did not load: ${err.message}`);
  }
}

async function checkLogo(endpoint, label) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 5000);
  try {
    const response = await fetch(`${baseUrl}${endpoint}`, { signal: controller.signal, cache: "no-store" });
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    const type = response.headers.get("content-type") || "";
    if (!type.startsWith("image/")) throw new Error(`unexpected content type ${type || "none"}`);
  } catch (err) {
    fail(`${label} did not load: ${err.message}`);
  } finally {
    clearTimeout(timeout);
  }
}

async function checkEventStream(endpoint, label) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 5000);
  let reader;
  try {
    const response = await fetch(`${baseUrl}${endpoint}`, {
      signal: controller.signal,
      headers: { accept: "text/event-stream" }
    });
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    reader = response.body?.getReader();
    const chunk = await reader?.read();
    const text = new TextDecoder().decode(chunk?.value || new Uint8Array());
    if (!text.includes("data:")) throw new Error("no initial event data");
  } catch (err) {
    fail(`${label} stream failed: ${err.message}`);
  } finally {
    if (reader) {
      try {
        await reader.cancel();
      } catch (_err) {}
    }
    clearTimeout(timeout);
  }
}

for (const file of [
  "Raceworks.js",
  "server.js",
  "public/control.html",
  "public/preflight.html",
  "public/program.html",
  "public/top-banner.html",
  "public/side-banner.html",
  "public/program-router.js",
  "public/fonts/Teko-SemiBold.ttf",
  "public/fonts/BarlowCondensed-Bold.ttf",
  "src/raceMonitor.js",
  "src/myRacePass.js"
]) {
  await checkFile(file);
}

let style = {};
try {
  const health = await getJson("/api/health");
  const timing = health.timing || {};
  const output = health.output || {};
  console.log(`Server: running on port ${health.port || "4000"}`);
  console.log(`Timing: ${timing.drivers || 0} driver(s), source ${timing.source || "none"}`);
  console.log(`Output: ${output.program || "blank"}, top banner ${output.topBannerVisible ? "shown" : "hidden"}`);
  if (!Number(timing.drivers || 0)) warn("No timing drivers are loaded yet. Connect timing before going live.");
} catch (err) {
  fail(`Server health check failed at ${baseUrl}: ${err.message}`);
}

try {
  const network = await getJson("/api/network");
  console.log(`Local URL: ${network.localBaseUrl || `${baseUrl}`}`);
  console.log(`LAN URL: ${network.lanBaseUrl || "not found"}`);
  if (!network.lanBaseUrl) warn("No LAN URL found. vMix/OBS/Wirecast on another computer may not reach this machine.");
} catch (err) {
  fail(`Network check failed: ${err.message}`);
}

try {
  style = await getJson("/api/style");
  const headerCount = Number(style.bannerHeaderLogoCount || 0);
  const sponsorCount = Number(style.bannerSponsorLogoCount || 0);
  console.log(`Banner template: ${style.bannerTemplate || "default"}`);
  console.log(`Banner mode: ${style.bannerRaceMode || "auto"}`);
  console.log(`Header logos: ${headerCount}; sponsor logos: ${sponsorCount}`);
  if (sponsorCount < 2) warn("Fewer than 2 sponsor logos are selected, so sponsor logo rotation will not be visible.");
  await checkLogo("/banner-logo/header/0", "Header logo");
  for (let index = 0; index < Math.max(1, sponsorCount); index += 1) {
    await checkLogo(`/banner-logo/sponsor/${index}`, `Sponsor logo ${index + 1}`);
  }
} catch (err) {
  fail(`Style/logo check failed: ${err.message}`);
}

for (const [endpoint, label] of [
  ["/control.html", "Control Center"],
  ["/preflight.html", "Pre-race Check"],
  ["/program.html", "Program overlay"],
  ["/top-banner.html", "Top banner"],
  ["/side-banner.html", "Side tower"],
  ["/current-leaders.html", "Current leaders"],
  ["/story-spotlight.html", "Race story"],
  ["/weather.html", "Weather"],
  ["/ticker.html", "Ticker"],
  ["/board.html?mode=results", "Results board"],
  ["/top3-podium.html", "Top 3"]
]) {
  await checkEndpoint(endpoint, label);
}

for (const [endpoint, label] of [
  ["/events", "Timing updates"],
  ["/program-events", "Program output updates"],
  ["/top-banner-events", "Top banner updates"]
]) {
  await checkEventStream(endpoint, label);
}

if (warnings.length) {
  console.log("\nWarnings:");
  for (const message of warnings) console.log(`- ${message}`);
}

if (failures.length) {
  console.error("\nPre-race check failed:");
  for (const message of failures) console.error(`- ${message}`);
  process.exit(1);
}

console.log("\nPre-race check passed.");
