import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import type { RouteContext } from "./types.js";
import type { DifficultyKey } from "../server/constants.js";
import { DIFFICULTIES } from "../server/constants.js";
import { ensureUserStats } from "../server/db-schema.js";
import { fetchStats } from "../server/data-fetchers.js";
import { recordAudit } from "../server/admin-service.js";

const paramsUserIdSchema = {
  type: "object" as const,
  required: ["userId"],
  properties: { userId: { type: "string", pattern: "^[0-9]+$" } },
};

const difficultySchema = { type: "string" as const, enum: DIFFICULTIES };

export const registerStatsRoutes = (
  app: FastifyInstance,
  ctx: RouteContext,
) => {
  const { db, requireAuth } = ctx;

  // GET /stats/:userId
  app.get(
    "/stats/:userId",
    {
      preHandler: requireAuth,
      schema: {
        params: paramsUserIdSchema,
      },
    },
    async (request, reply) => {
      const userId = Number((request.params as { userId: string }).userId);
      const tokenUserId = request.user?.sub;
      if (!Number.isFinite(userId)) {
        reply.code(400);
        return { error: "userId invalide" };
      }
      if (!tokenUserId || tokenUserId !== userId) {
        reply.code(403);
        return { error: "Acces refuse" };
      }
      const stats = await fetchStats(db, userId);
      return { stats };
    },
  );

  // POST /stats/:userId/level-complete
  app.post(
    "/stats/:userId/level-complete",
    {
      preHandler: requireAuth,
      config: { rateLimit: { max: 120, timeWindow: "1 minute" } },
      schema: {
        params: paramsUserIdSchema,
        body: {
          type: "object",
          required: ["difficulty", "levelId", "moves", "time"],
          additionalProperties: false,
          properties: {
            difficulty: difficultySchema,
            levelId: { type: "integer", minimum: 0 },
            moves: { type: "integer", minimum: 1 },
            time: { type: "integer", minimum: 500 },
          },
        },
      },
    },
    async (request, reply) => {
      const userId = Number((request.params as { userId: string }).userId);
      const body = request.body as
        | {
            difficulty?: DifficultyKey;
            levelId?: number;
            moves?: number;
            time?: number;
          }
        | undefined;
      const tokenUserId = request.user?.sub;
      if (!Number.isFinite(userId)) {
        reply.code(400);
        return { error: "userId invalide" };
      }
      if (!tokenUserId || tokenUserId !== userId) {
        reply.code(403);
        return { error: "Acces refuse" };
      }
      const difficulty = body?.difficulty;
      const levelId = Number(body?.levelId);
      const moves = Number(body?.moves);
      const time = Number(body?.time);
      if (
        !difficulty ||
        !DIFFICULTIES.includes(difficulty) ||
        !Number.isFinite(levelId) ||
        levelId < 0 ||
        !Number.isFinite(moves) ||
        moves < 1 ||
        !Number.isFinite(time) ||
        time < 500
      ) {
        reply.code(400);
        return { error: "Payload invalide" };
      }
      await ensureUserStats(db, userId);
      await db.execute(
        `INSERT INTO level_stats (user_id, difficulty, level_id, completions, last_moves, last_time, best_moves, best_time)
         VALUES (?, ?, ?, 1, ?, ?, ?, ?)
         ON DUPLICATE KEY UPDATE
           completions = completions + 1,
           last_moves = VALUES(last_moves),
           last_time = VALUES(last_time),
           best_moves = IF(best_moves IS NULL OR VALUES(best_moves) < best_moves, VALUES(best_moves), best_moves),
           best_time = IF(best_time IS NULL OR VALUES(best_time) < best_time, VALUES(best_time), best_time)`,
        [userId, difficulty, levelId, moves, time, moves, time],
      );
      await db.execute(
        "UPDATE user_stats SET total_completions = total_completions + 1, total_moves = total_moves + ?, total_time = total_time + ? WHERE user_id = ?",
        [moves, time, userId],
      );
      await db.execute(
        "INSERT INTO recent_runs (user_id, difficulty, level_id, moves, time, completed_at) VALUES (?, ?, ?, ?, ?, NOW())",
        [userId, difficulty, levelId, moves, time],
      );
      const [overflowRows] = await db.execute(
        "SELECT id FROM recent_runs WHERE user_id = ? ORDER BY completed_at DESC, id DESC LIMIT 6, 1000",
        [userId],
      );
      if (Array.isArray(overflowRows) && overflowRows.length > 0) {
        const ids = overflowRows
          .map((row) => (row as { id?: number }).id)
          .filter((id): id is number => typeof id === "number");
        if (ids.length > 0) {
          await db.execute(
            `DELETE FROM recent_runs WHERE id IN (${ids
              .map(() => "?")
              .join(",")})`,
            ids,
          );
        }
      }
      await recordAudit(db, {
        userId,
        action: "stats.level.complete",
        meta: { difficulty, levelId, moves, time },
        ip: request.ip,
        userAgent: request.headers["user-agent"]?.toString() ?? null,
        installationId: request.headers["x-installation-id"]?.toString() ?? null,
      });
      const stats = await fetchStats(db, userId);
      return { stats };
    },
  );

  // POST /stats/:userId/tutorial
  app.post(
    "/stats/:userId/tutorial",
    {
      preHandler: requireAuth,
      schema: {
        params: paramsUserIdSchema,
      },
    },
    async (request, reply) => {
      const userId = Number((request.params as { userId: string }).userId);
      const tokenUserId = request.user?.sub;
      if (!Number.isFinite(userId)) {
        reply.code(400);
        return { error: "userId invalide" };
      }
      if (!tokenUserId || tokenUserId !== userId) {
        reply.code(403);
        return { error: "Acces refuse" };
      }
      await ensureUserStats(db, userId);
      await db.execute(
        "UPDATE user_stats SET tutorial_completed = TRUE WHERE user_id = ?",
        [userId],
      );
      await recordAudit(db, {
        userId,
        action: "stats.tutorial.complete",
        ip: request.ip,
        userAgent: request.headers["user-agent"]?.toString() ?? null,
        installationId: request.headers["x-installation-id"]?.toString() ?? null,
      });
      const stats = await fetchStats(db, userId);
      return { stats };
    },
  );
};
