import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import type { RouteContext } from "./types.js";
import type { AidKind, SettingsState } from "../server/types.js";
import type { DifficultyKey } from "../server/constants.js";
import { DIFFICULTIES, REWARD_POINTS, AID_PACKS } from "../server/constants.js";
import {
  ensureWallet,
  ensureUserSettings,
  ensureProgressRows,
} from "../server/db-schema.js";
import {
  fetchWallet,
  fetchSettings,
  fetchProgress,
} from "../server/data-fetchers.js";
import { recordAudit, recordWalletChange } from "../server/admin-service.js";
import type { WalletChange } from "../server/admin-service.js";

const paramsUserIdSchema = {
  type: "object" as const,
  required: ["userId"],
  properties: { userId: { type: "string", pattern: "^[0-9]+$" } },
};

const bodyUserIdSchema = {
  type: "object" as const,
  properties: { userId: { type: "number" as const } },
};

const difficultySchema = { type: "string" as const, enum: DIFFICULTIES };
const packIdSchema = {
  type: "string" as const,
  enum: AID_PACKS.map((p) => p.id),
};
const aidKindSchema = {
  type: "string" as const,
  enum: ["hint", "undo", "replay"] as const,
};

// NOTE: Le jeu ne pénalise plus les points gagnés quand une solution est utilisée.

export const registerWalletRoutes = (
  app: FastifyInstance,
  ctx: RouteContext,
) => {
  const { db, requireAuth } = ctx;

  // GET /wallet/:userId
  app.get(
    "/wallet/: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" };
      }
      await ensureWallet(db, userId);
      return { wallet: await fetchWallet(db, userId) };
    },
  );

  // POST /wallet/reward
  app.post(
    "/wallet/reward",
    {
      preHandler: requireAuth,
      schema: {
        body: {
          ...bodyUserIdSchema,
          required: ["userId", "difficulty"],
          properties: {
            ...bodyUserIdSchema.properties,
            difficulty: difficultySchema,
            usedAutoSolve: { type: "boolean" },
          },
        },
      },
    },
    async (request, reply) => {
      const body = request.body as
        | {
            userId?: number;
            difficulty?: DifficultyKey;
            usedAutoSolve?: boolean;
          }
        | undefined;
      const userId = Number(body?.userId);
      const difficulty = body?.difficulty;
      const usedAutoSolve = body?.usedAutoSolve ?? false;
      const tokenUserId = request.user?.sub;
      if (
        !Number.isFinite(userId) ||
        !difficulty ||
        !DIFFICULTIES.includes(difficulty)
      ) {
        reply.code(400);
        return { error: "Payload invalide" };
      }
      if (!tokenUserId || tokenUserId !== userId) {
        reply.code(403);
        return { error: "Acces refuse" };
      }
      const reward = REWARD_POINTS[difficulty];
      await db.execute(
        "UPDATE wallets SET points = points + ? WHERE user_id = ?",
        [reward, userId],
      );
      await recordWalletChange(
        db,
        userId,
        [{ resource: "points", delta: reward }],
        "wallet.reward",
        { difficulty, usedAutoSolve },
      );
      await recordAudit(db, {
        userId,
        action: "wallet.reward",
        meta: { difficulty, reward, usedAutoSolve },
        ip: request.ip,
        userAgent: request.headers["user-agent"]?.toString() ?? null,
        installationId:
          request.headers["x-installation-id"]?.toString() ?? null,
      });
      return { wallet: await fetchWallet(db, userId), gained: reward };
    },
  );

  // POST /wallet/purchase
  app.post(
    "/wallet/purchase",
    {
      preHandler: requireAuth,
      schema: {
        body: {
          ...bodyUserIdSchema,
          required: ["userId", "packId"],
          properties: {
            ...bodyUserIdSchema.properties,
            packId: packIdSchema,
          },
        },
      },
    },
    async (request, reply) => {
      const body = request.body as
        | { userId?: number; packId?: string }
        | undefined;
      const userId = Number(body?.userId);
      const tokenUserId = request.user?.sub;
      if (!Number.isFinite(userId) || !body?.packId) {
        reply.code(400);
        return { error: "Payload invalide" };
      }
      if (!tokenUserId || tokenUserId !== userId) {
        reply.code(403);
        return { error: "Acces refuse" };
      }
      const pack = AID_PACKS.find((item) => item.id === body.packId);
      if (!pack) {
        reply.code(404);
        return { error: "Pack introuvable" };
      }

      // ✅ SÉCURISÉ: Vérification atomique des points dans l'UPDATE
      // Cela prévient les race conditions: si les points sont insuffisants,
      // l'UPDATE ne modifie aucune ligne (affectedRows = 0)
      const [result] = await db.execute(
        "UPDATE wallets SET points = points - ?, hints = hints + ?, undos = undos + ?, replays = replays + ? WHERE user_id = ? AND points >= ?",
        [
          pack.cost,
          pack.inventory.hints,
          pack.inventory.undos,
          pack.inventory.replays,
          userId,
          pack.cost,
        ],
      );

      // Si aucune ligne n'a été modifiée, les points sont insuffisants
      if ((result as any).affectedRows === 0) {
        const wallet = await fetchWallet(db, userId);
        reply.code(402);
        return { error: "Points insuffisants", wallet };
      }
      await recordWalletChange(
        db,
        userId,
        (
          [
            { resource: "points" as const, delta: -pack.cost },
            { resource: "hints" as const, delta: pack.inventory.hints },
            { resource: "undos" as const, delta: pack.inventory.undos },
            { resource: "replays" as const, delta: pack.inventory.replays },
          ] satisfies WalletChange[]
        ).filter((c) => c.delta !== 0),
        "wallet.purchase",
        { packId: pack.id },
      );
      await recordAudit(db, {
        userId,
        action: "wallet.purchase",
        meta: { packId: pack.id, cost: pack.cost },
        ip: request.ip,
        userAgent: request.headers["user-agent"]?.toString() ?? null,
        installationId:
          request.headers["x-installation-id"]?.toString() ?? null,
      });
      return { wallet: await fetchWallet(db, userId), pack };
    },
  );

  // POST /wallet/consume
  app.post(
    "/wallet/consume",
    {
      preHandler: requireAuth,
      schema: {
        body: {
          ...bodyUserIdSchema,
          required: ["userId", "kind"],
          properties: {
            ...bodyUserIdSchema.properties,
            kind: aidKindSchema,
          },
        },
      },
    },
    async (request, reply) => {
      const body = request.body as
        | { userId?: number; kind?: AidKind }
        | undefined;
      const userId = Number(body?.userId);
      const kind = body?.kind;
      const tokenUserId = request.user?.sub;
      if (!Number.isFinite(userId) || !kind) {
        reply.code(400);
        return { error: "Payload invalide" };
      }
      if (!tokenUserId || tokenUserId !== userId) {
        reply.code(403);
        return { error: "Acces refuse" };
      }
      const wallet = await fetchWallet(db, userId);
      const available =
        kind === "hint"
          ? wallet.inventory.hints
          : kind === "undo"
            ? wallet.inventory.undos
            : wallet.inventory.replays;
      if (available <= 0) {
        reply.code(402);
        return { error: "Aide indisponible", wallet };
      }
      const column =
        kind === "hint" ? "hints" : kind === "undo" ? "undos" : "replays";
      await db.execute(
        `UPDATE wallets SET ${column} = ${column} - 1 WHERE user_id = ?`,
        [userId],
      );
      await recordWalletChange(
        db,
        userId,
        [{ resource: column as "hints" | "undos" | "replays", delta: -1 }],
        "wallet.consume",
        { kind },
      );
      await recordAudit(db, {
        userId,
        action: "wallet.consume",
        meta: { kind },
        ip: request.ip,
        userAgent: request.headers["user-agent"]?.toString() ?? null,
        installationId:
          request.headers["x-installation-id"]?.toString() ?? null,
      });
      return { wallet: await fetchWallet(db, userId) };
    },
  );
};

export const registerSettingsRoutes = (
  app: FastifyInstance,
  ctx: RouteContext,
) => {
  const { db, requireAuth } = ctx;

  // Constantes des valeurs valides pour les réglages
  const VALID_THEMES = ["ocean", "sunset", "neon"] as const;
  const VALID_ANIMATION_SPEEDS = ["slow", "normal", "fast"] as const;

  // GET /settings/:userId
  app.get(
    "/settings/: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 settings = await fetchSettings(db, userId);
      return { settings };
    },
  );

  // PUT /settings/:userId
  app.put(
    "/settings/:userId",
    {
      preHandler: requireAuth,
      schema: {
        params: paramsUserIdSchema,
        body: {
          type: "object",
          required: [
            "music",
            "sfx",
            "haptics",
            "theme",
            "animationSpeed",
            "sfxVolume",
            "musicVolume",
          ],
          additionalProperties: false,
          properties: {
            music: { type: "boolean" },
            sfx: { type: "boolean" },
            haptics: { type: "boolean" },
            // ✅ SÉCURISÉ: Validation enum stricte pour les thèmes
            theme: { type: "string", enum: ["ocean", "sunset", "neon"] },
            // ✅ SÉCURISÉ: Validation enum stricte pour les vitesses d'animation
            animationSpeed: {
              type: "string",
              enum: ["slow", "normal", "fast"],
            },
            sfxVolume: { type: "number", minimum: 0, maximum: 1 },
            musicVolume: { type: "number", minimum: 0, maximum: 1 },
            ballSkin: { type: ["string", "null"] },
          },
        },
      },
    },
    async (request, reply) => {
      const userId = Number((request.params as { userId: string }).userId);
      const body = request.body as SettingsState | 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" };
      }
      if (!body) {
        reply.code(400);
        return { error: "Payload invalide" };
      }

      // ✅ Validation manuelle supplémentaire (défense en profondeur)
      if (!VALID_THEMES.includes(body.theme as any)) {
        reply.code(400);
        return { error: "theme invalide" };
      }

      if (!VALID_ANIMATION_SPEEDS.includes(body.animationSpeed as any)) {
        reply.code(400);
        return { error: "animationSpeed invalide" };
      }

      let ballSkin: string | null = null;
      if (typeof (body as any).ballSkin === "string") {
        const rawBallSkin = (body as any).ballSkin.trim();
        if (rawBallSkin && rawBallSkin !== "default") {
          const [rows] = await db.execute(
            "SELECT 1 FROM user_ball_skins WHERE user_id = ? AND skin_id = ? LIMIT 1",
            [userId, rawBallSkin],
          );
          const owns = Array.isArray(rows) && rows.length > 0;
          if (!owns) {
            reply.code(403);
            return { error: "Bille non debloquee" };
          }
          ballSkin = rawBallSkin;
        } else {
          ballSkin = null;
        }
      }

      await ensureUserSettings(db, userId);
      await db.execute(
        "UPDATE user_settings SET music = ?, sfx = ?, haptics = ?, theme = ?, animation_speed = ?, sfx_volume = ?, music_volume = ?, ball_skin = ? WHERE user_id = ?",
        [
          body.music,
          body.sfx,
          body.haptics,
          body.theme,
          body.animationSpeed,
          body.sfxVolume,
          body.musicVolume,
          ballSkin,
          userId,
        ],
      );
      await recordAudit(db, {
        userId,
        action: "settings.update",
        ip: request.ip,
        userAgent: request.headers["user-agent"]?.toString() ?? null,
        installationId:
          request.headers["x-installation-id"]?.toString() ?? null,
      });
      const settings = await fetchSettings(db, userId);
      return { settings };
    },
  );
};

export const registerProgressRoutes = (
  app: FastifyInstance,
  ctx: RouteContext,
) => {
  const { db, requireAuth } = ctx;

  // GET /progress/:userId
  app.get(
    "/progress/: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" };
      }
      await ensureProgressRows(db, userId);
      return { progress: await fetchProgress(db, userId) };
    },
  );

  // POST /progress/:userId/increment
  app.post(
    "/progress/:userId/increment",
    {
      preHandler: requireAuth,
      schema: {
        params: paramsUserIdSchema,
        body: {
          type: "object",
          required: ["difficulty"],
          additionalProperties: false,
          properties: {
            difficulty: difficultySchema,
          },
        },
      },
    },
    async (request, reply) => {
      const userId = Number((request.params as { userId: string }).userId);
      const body = request.body as { difficulty?: DifficultyKey } | undefined;
      const difficulty = body?.difficulty;
      const tokenUserId = request.user?.sub;
      if (
        !Number.isFinite(userId) ||
        !difficulty ||
        !DIFFICULTIES.includes(difficulty)
      ) {
        reply.code(400);
        return { error: "Payload invalide" };
      }
      if (!tokenUserId || tokenUserId !== userId) {
        reply.code(403);
        return { error: "Acces refuse" };
      }
      await db.execute(
        "INSERT INTO progress (user_id, difficulty, completed) VALUES (?, ?, 1) ON DUPLICATE KEY UPDATE completed = completed + 1",
        [userId, difficulty],
      );
      await recordAudit(db, {
        userId,
        action: "progress.increment",
        meta: { difficulty },
        ip: request.ip,
        userAgent: request.headers["user-agent"]?.toString() ?? null,
        installationId:
          request.headers["x-installation-id"]?.toString() ?? null,
      });
      return { progress: await fetchProgress(db, userId) };
    },
  );
};
