const http = require("http");
const fs = require("fs");
const fsp = require("fs/promises");
const path = require("path");

const HOST = process.env.HOST || "0.0.0.0";
const PORT = process.env.PORT || 3000;
const ROOT = __dirname;
const LEADERBOARD_FILE = path.join(ROOT, "leaderboard.json");
const MAX_ENTRIES = 5;
const STATIC_FILES = new Map([
  ["/", "index.html"],
  ["/index.html", "index.html"],
  ["/style.css", "style.css"],
  ["/game.js", "game.js"],
  ["/Menu.mp3", "Menu.mp3"],
  ["/Game.mp3", "Game.mp3"],
  ["/Logo-8-bit.png", "Logo-8-bit.png"],
  ["/hand-cursor.png", "hand-cursor.png"],
  ["/hand.png", "hand.png"],
  ["/Fish.png", "Fish.png"],
  ["/bubble.jpg", "bubble.jpg"],
  ["/inspo.webp", "inspo.webp"],
  ["/MOF%20Fish%20Master.svg", "MOF Fish Master.svg"],
  ["/MOF Fish Master.svg", "MOF Fish Master.svg"],
]);

async function ensureLeaderboardFile() {
  try {
    await fsp.access(LEADERBOARD_FILE, fs.constants.F_OK);
  } catch {
    await fsp.writeFile(LEADERBOARD_FILE, "[]\n", "utf8");
  }
}

async function readLeaderboard() {
  await ensureLeaderboardFile();
  const raw = await fsp.readFile(LEADERBOARD_FILE, "utf8");

  try {
    const parsed = JSON.parse(raw);
    if (!Array.isArray(parsed)) {
      return [];
    }

    return sanitizeEntries(parsed);
  } catch {
    return [];
  }
}

async function writeLeaderboard(entries) {
  const sanitized = sanitizeEntries(entries);
  await fsp.writeFile(LEADERBOARD_FILE, `${JSON.stringify(sanitized, null, 2)}\n`, "utf8");
  return sanitized;
}

function sanitizeEntries(entries) {
  const deduped = new Map();

  for (const entry of entries) {
    const sanitized = {
      name: sanitizeName(entry.name),
      score: sanitizeScore(entry.score),
      createdAt: typeof entry.createdAt === "string" ? entry.createdAt : new Date().toISOString(),
    };

    if (!sanitized.name || sanitized.score <= 0) {
      continue;
    }

    const key = sanitized.name.toLowerCase();
    const existing = deduped.get(key);
    if (!existing) {
      deduped.set(key, sanitized);
      continue;
    }

    if (sanitized.score > existing.score) {
      deduped.set(key, sanitized);
      continue;
    }

    if (sanitized.score === existing.score) {
      const existingTime = new Date(existing.createdAt).getTime();
      const sanitizedTime = new Date(sanitized.createdAt).getTime();
      if (sanitizedTime < existingTime) {
        deduped.set(key, sanitized);
      }
    }
  }

  return Array.from(deduped.values())
    .sort((a, b) => {
      if (b.score !== a.score) {
        return b.score - a.score;
      }

      return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
    })
    .slice(0, MAX_ENTRIES);
}

function sanitizeName(value) {
  return String(value || "")
    .replace(/[^a-z0-9 _-]/gi, "")
    .trim()
    .slice(0, 12);
}

function sanitizeScore(value) {
  const score = Number.parseInt(value, 10);
  if (!Number.isFinite(score) || score < 0) {
    return 0;
  }

  return score;
}

function sendJson(response, status, payload) {
  response.writeHead(status, {
    "Content-Type": "application/json; charset=utf-8",
    "Cache-Control": "no-store",
  });
  response.end(JSON.stringify(payload));
}

function sendFile(response, filePath) {
  const ext = path.extname(filePath);
  const contentType = {
    ".html": "text/html; charset=utf-8",
    ".css": "text/css; charset=utf-8",
    ".js": "application/javascript; charset=utf-8",
    ".mp3": "audio/mpeg",
    ".png": "image/png",
    ".jpg": "image/jpeg",
    ".webp": "image/webp",
    ".svg": "image/svg+xml",
    ".json": "application/json; charset=utf-8",
  }[ext] || "application/octet-stream";

  const stream = fs.createReadStream(filePath);
  stream.on("error", () => {
    response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
    response.end("Not found");
  });

  response.writeHead(200, { "Content-Type": contentType });
  stream.pipe(response);
}

async function handleApi(request, response) {
  if (request.method === "GET" && request.url === "/api/leaderboard") {
    const leaderboard = await readLeaderboard();
    sendJson(response, 200, { entries: leaderboard });
    return;
  }

  if (request.method === "POST" && request.url === "/api/leaderboard") {
    const body = await readRequestBody(request);

    let payload;
    try {
      payload = JSON.parse(body || "{}");
    } catch {
      sendJson(response, 400, { error: "Invalid JSON payload." });
      return;
    }

    const name = sanitizeName(payload.name);
    const score = sanitizeScore(payload.score);
    if (!name || score <= 0) {
      sendJson(response, 400, { error: "A name and positive score are required." });
      return;
    }

    const leaderboard = await readLeaderboard();
    leaderboard.push({
      name,
      score,
      createdAt: new Date().toISOString(),
    });

    const saved = await writeLeaderboard(leaderboard);
    sendJson(response, 201, { entries: saved });
    return;
  }

  sendJson(response, 404, { error: "Not found" });
}

function readRequestBody(request) {
  return new Promise((resolve, reject) => {
    let data = "";

    request.on("data", (chunk) => {
      data += chunk;
      if (data.length > 1e6) {
        reject(new Error("Payload too large"));
      }
    });
    request.on("end", () => resolve(data));
    request.on("error", reject);
  });
}

const server = http.createServer(async (request, response) => {
  try {
    if (!request.url) {
      response.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
      response.end("Bad request");
      return;
    }

    if (request.url.startsWith("/api/")) {
      await handleApi(request, response);
      return;
    }

    const asset = STATIC_FILES.get(request.url);
    if (!asset) {
      response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
      response.end("Not found");
      return;
    }

    sendFile(response, path.join(ROOT, asset));
  } catch (error) {
    response.writeHead(500, { "Content-Type": "application/json; charset=utf-8" });
    response.end(JSON.stringify({ error: error.message || "Server error" }));
  }
});

ensureLeaderboardFile()
  .then(() => {
    server.listen(PORT, HOST, () => {
      console.log(`Flappy Fish running on ${HOST}:${PORT}`);
    });
  })
  .catch((error) => {
    console.error("Failed to initialise leaderboard file.", error);
    process.exitCode = 1;
  });
