import { Router, type Request, type Response } from "express";
import { getLeaderboardStore, getMetricsStore, getSessionStore } from "../lib/storage";
import { isCleanNickname } from "../lib/profanity";
import { captureBackendRouteError } from "../lib/telemetry/capture";
import type { Difficulty } from "../../../shared/types/game";
import {
  DEFAULT_PLATFORM_ID,
  PLATFORM_IDS,
  type PlatformId,
} from "../../../shared/types/platform";
import type { LeaderboardEntry } from "../../../shared/types/leaderboard";
import {
  MAX_SCORE_PER_DIMENSION,
  MAX_TOTAL_SCORE,
  type Score,
  scoreToGrade,
} from "../../../shared/types/scoring";

export const scoresRouter = Router();
const PERSISTENT_LEADERBOARD_ENABLED = process.env.PERSISTENT_LEADERBOARD_ENABLED === "true";

const VALID_DIFFICULTIES: Difficulty[] = ["easy", "medium", "hard"];
const SCORE_DIMENSIONS = [
  "efficiency",
  "safety",
  "documentation",
  "accuracy",
] as const;
type ScoreDimension = (typeof SCORE_DIMENSIONS)[number];
const isRecord = (value: unknown): value is Record<string, unknown> =>
  typeof value === "object" && value !== null && !Array.isArray(value);
const isBoundedScoreValue = (value: number, max: number): boolean =>
  Number.isFinite(value) && value >= 0 && value <= max;

function isScoreDimension(value: unknown): value is ScoreDimension {
  return typeof value === "string" && SCORE_DIMENSIONS.includes(value as ScoreDimension);
}

function parsePlatformQuery(value: unknown): PlatformId | null {
  if (typeof value !== "string") {
    return null;
  }
  return PLATFORM_IDS.includes(value as PlatformId)
    ? (value as PlatformId)
    : null;
}

function calculateScoreFromEvents(rawEvents: unknown): Score | null {
  if (!Array.isArray(rawEvents)) {
    return null;
  }

  const score: Score = {
    efficiency: 0,
    safety: 0,
    documentation: 0,
    accuracy: 0,
    total: 0,
  };

  for (const rawEvent of rawEvents) {
    if (!isRecord(rawEvent)) {
      continue;
    }
    const type = rawEvent.type;
    const dimension = rawEvent.dimension;
    const points = rawEvent.points;
    if (
      (type !== "bonus" && type !== "penalty") ||
      !isScoreDimension(dimension) ||
      typeof points !== "number" ||
      !Number.isFinite(points) ||
      points < 0
    ) {
      continue;
    }
    const delta = type === "bonus" ? points : -points;
    score[dimension] = Math.max(
      0,
      Math.min(MAX_SCORE_PER_DIMENSION, score[dimension] + delta),
    );
  }

  score.total =
    score.efficiency + score.safety + score.documentation + score.accuracy;
  if (!isBoundedScoreValue(score.total, MAX_TOTAL_SCORE)) {
    return null;
  }

  return score;
}

scoresRouter.get("/", async (req: Request, res: Response) => {
  try {
    const difficulty = req.query.difficulty as Difficulty | undefined;
    const requestedPlatform =
      req.query.platform === undefined
        ? undefined
        : parsePlatformQuery(req.query.platform);
    const platform = requestedPlatform ?? DEFAULT_PLATFORM_ID;

    if (difficulty && !VALID_DIFFICULTIES.includes(difficulty)) {
      res.status(400).json({
        error: "Invalid difficulty. Must be easy, medium, or hard.",
      });
      return;
    }
    if (req.query.platform !== undefined && requestedPlatform === null) {
      res.status(400).json({
        error: `Invalid platform. Must be ${PLATFORM_IDS.join(", ")}.`,
      });
      return;
    }

    const leaderboard = getLeaderboardStore();
    const entries = await leaderboard.getLeaderboard({
      platform,
      ...(difficulty ? { difficulty } : {}),
    });
    const hallOfFame = await leaderboard.getHallOfFame(platform);

    res.json({ entries, hallOfFame });
  } catch (error) {
    captureBackendRouteError(req, error);
    res.status(500).json({ error: "Failed to fetch leaderboard" });
  }
});

scoresRouter.post("/", async (req: Request, res: Response) => {
  try {
    if (!isRecord(req.body)) {
      res.status(400).json({ error: "Invalid request body" });
      return;
    }

    const { sessionToken, nickname } = req.body;

    if (!sessionToken || typeof sessionToken !== "string") {
      res.status(400).json({ error: "Session token is required" });
      return;
    }

    const existingSession = await getSessionStore().get(sessionToken);
    if (!existingSession || existingSession.used) {
      res.status(403).json({
        error: "Invalid or already used session token",
      });
      return;
    }
    if (
      existingSession.identityKind === "github" &&
      !existingSession.githubLogin?.trim()
    ) {
      res.status(409).json({ error: "GitHub session identity is incomplete" });
      return;
    }
    const effectiveNickname = existingSession.identityKind === "github"
      ? existingSession.githubLogin
      : nickname;
    const normalizedNickname =
      typeof effectiveNickname === "string" ? effectiveNickname.trim() : "";

    if (!normalizedNickname) {
      res.status(400).json({ error: "Nickname is required" });
      return;
    }
    const maxNicknameLength = existingSession.identityKind === "github" ? 39 : 20;
    if (normalizedNickname.length > maxNicknameLength) {
      res.status(400).json({
        error: `Nickname must be ${maxNicknameLength} characters or less`,
      });
      return;
    }
    if (existingSession.identityKind !== "github") {
      const nicknameCheck = isCleanNickname(normalizedNickname);
      if (!nicknameCheck.clean) {
        res.status(400).json({ error: nicknameCheck.reason });
        return;
      }
    }

    const gameplayRecord = await getMetricsStore().getLatestCompletedBySessionToken(sessionToken);
    if (!gameplayRecord) {
      res.status(409).json({ error: "Completion telemetry is required before submitting a score" });
      return;
    }
    if (
      gameplayRecord.platform &&
      gameplayRecord.platform !== existingSession.platform
    ) {
      res.status(409).json({ error: "Session telemetry mismatch" });
      return;
    }
    if (
      gameplayRecord.difficulty &&
      gameplayRecord.difficulty !== existingSession.difficulty
    ) {
      res.status(409).json({ error: "Session telemetry mismatch" });
      return;
    }
    if (
      gameplayRecord.scenarioTitle &&
      gameplayRecord.scenarioTitle !== existingSession.scenarioTitle
    ) {
      res.status(409).json({ error: "Session telemetry mismatch" });
      return;
    }

    const score = calculateScoreFromEvents(gameplayRecord.scoringEvents);
    if (!score) {
      res.status(409).json({ error: "Completion telemetry has invalid scoring data" });
      return;
    }
    const commandCount = Number.isFinite(gameplayRecord.commandCount)
      ? Math.max(0, Math.trunc(gameplayRecord.commandCount ?? 0))
      : (gameplayRecord.commandsExecuted?.length ?? 0);
    const grade = scoreToGrade(score.total);

    const session = await getSessionStore().validateAndConsume(sessionToken);
    if (!session) {
      res.status(403).json({
        error: "Invalid or already used session token",
      });
      return;
    }

    const durationMs = Math.max(0, Date.now() - session.startTime);

    if (
      !PERSISTENT_LEADERBOARD_ENABLED ||
      !session.persistentScoreEligible ||
      session.identityKind !== "github" ||
      !session.githubUserId
    ) {
      res.status(200).json({
        saved: false,
        mode: "ephemeral",
        nickname: normalizedNickname,
        platform: session.platform,
        difficulty: session.difficulty,
        score,
        grade,
        commandCount,
        durationMs,
        scenarioTitle: session.scenarioTitle,
        timestamp: Date.now(),
      });
      return;
    }

    const entry: LeaderboardEntry = {
      id: crypto.randomUUID(),
      nickname: normalizedNickname,
      platform: session.platform,
      difficulty: session.difficulty,
      score,
      grade,
      commandCount,
      durationMs,
      scenarioTitle: session.scenarioTitle,
      identityKind: "github",
      githubUserId: session.githubUserId,
      githubLogin: session.githubLogin ?? undefined,
      trafficSource: session.trafficSource,
      timestamp: Date.now(),
    };

    const saved = await getLeaderboardStore().addEntry(entry);
    res.status(201).json({
      ...saved,
      saved: true,
      mode: "persistent",
    });
  } catch (error) {
    captureBackendRouteError(req, error);
    res.status(500).json({ error: "Failed to save score" });
  }
});
