/**
 * Routes pour les bonus quotidien et arcade
 */

import type { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
import type { RowDataPacket } from "mysql2/promise";
import type { RouteContext } from "./types.js";
import { recordWalletChange } from "../server/admin-service.js";

// Types pour les bonus
interface DailyBonusRow extends RowDataPacket {
  user_id: number;
  last_claim_date: string | null;
  consecutive_days: number;
  total_hints_earned: number;
  total_solutions_earned: number;
}

interface ArcadeBonusRow extends RowDataPacket {
  user_id: number;
  total_games_played: number;
  last_bonus_at: number;
  total_hints_earned: number;
}

// Constantes
const HINTS_PER_DAY = 3;
const STREAK_FOR_SOLUTION = 7;
const GAMES_FOR_BONUS = 10;
const HINTS_PER_ARCADE_BONUS = 1;

/**
 * Helper pour obtenir la date du jour au format YYYY-MM-DD en heure de Paris
 */
const getTodayKey = (): string => {
  // Utiliser l'heure de Paris (Europe/Paris)
  const dateString = new Date().toLocaleString("en-CA", {
    timeZone: "Europe/Paris",
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
  });
  return dateString.split(",")[0]; // Format: 2026-02-08
};

/**
 * Helper pour obtenir la date d'hier en heure de Paris
 */
const getYesterdayKey = (): string => {
  const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
  const dateString = yesterday.toLocaleString("en-CA", {
    timeZone: "Europe/Paris",
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
  });
  return dateString.split(",")[0]; // Format: 2026-02-07
};

export const registerBonusRoutes = (
  app: FastifyInstance,
  ctx: RouteContext,
) => {
  const { db, requireAuth } = ctx;

  // ========================
  // BONUS QUOTIDIEN
  // ========================

  /**
   * GET /bonus/daily - Obtenir l'état du bonus quotidien
   */
  app.get(
    "/bonus/daily",
    { preHandler: requireAuth },
    async (request: FastifyRequest, reply: FastifyReply) => {
      const userId = request.user?.sub;
      if (!userId) {
        return reply.status(401).send({ error: "Non authentifié" });
      }
      const today = getTodayKey();
      const yesterday = getYesterdayKey();

      try {
        // Récupérer ou créer l'entrée bonus
        const [rows] = await db.query<DailyBonusRow[]>(
          "SELECT * FROM daily_bonus WHERE user_id = ?",
          [userId],
        );

        let bonus = rows[0];
        if (!bonus) {
          // Créer l'entrée si elle n'existe pas
          await db.query(
            "INSERT INTO daily_bonus (user_id, last_claim_date, consecutive_days) VALUES (?, NULL, 0)",
            [userId],
          );
          bonus = {
            user_id: userId,
            last_claim_date: null,
            consecutive_days: 0,
            total_hints_earned: 0,
            total_solutions_earned: 0,
          } as DailyBonusRow;
        }

        // Convertir last_claim_date au format YYYY-MM-DD (MySQL peut retourner un objet Date)
        const lastClaimDate = bonus.last_claim_date
          ? new Date(bonus.last_claim_date)
              .toLocaleString("en-CA", {
                timeZone: "Europe/Paris",
                year: "numeric",
                month: "2-digit",
                day: "2-digit",
              })
              .split(",")[0]
          : null;
        const canClaimToday = lastClaimDate !== today;

        // Afficher uniquement le streak déjà validé (pas de +1 anticipé avant claim).
        // Si un jour a été manqué, on réinitialise l'affichage à 0.
        let currentStreak = bonus.consecutive_days;
        if (canClaimToday && lastClaimDate !== null && lastClaimDate !== yesterday) {
          currentStreak = 0;
        }

        return reply.send({
          canClaimToday,
          currentStreak,
          hintsPerDay: HINTS_PER_DAY,
          daysUntilSolution: Math.max(0, STREAK_FOR_SOLUTION - currentStreak),
          solutionAvailable: currentStreak >= STREAK_FOR_SOLUTION,
          totalHintsEarned: bonus.total_hints_earned,
          totalSolutionsEarned: bonus.total_solutions_earned,
        });
      } catch (error) {
        app.log.error(error, "Erreur récupération bonus quotidien");
        return reply.status(500).send({ error: "Erreur serveur" });
      }
    },
  );

  /**
   * POST /bonus/daily/claim - Réclamer le bonus quotidien
   */
  app.post(
    "/bonus/daily/claim",
    {
      preHandler: requireAuth,
      config: {
        rateLimit: {
          max: 10,
          timeWindow: "1 minute",
        },
      },
    },
    async (request: FastifyRequest, reply: FastifyReply) => {
      const userId = request.user?.sub;
      if (!userId) {
        return reply.status(401).send({ error: "Non authentifié" });
      }
      const today = getTodayKey();
      const yesterday = getYesterdayKey();

      const connection = await db.getConnection();
      try {
        await connection.beginTransaction();

        // Récupérer l'état actuel
        const [rows] = await connection.query<DailyBonusRow[]>(
          "SELECT * FROM daily_bonus WHERE user_id = ? FOR UPDATE",
          [userId],
        );

        let bonus = rows[0];
        if (!bonus) {
          await connection.query(
            "INSERT INTO daily_bonus (user_id, last_claim_date, consecutive_days) VALUES (?, NULL, 0)",
            [userId],
          );
          bonus = {
            user_id: userId,
            last_claim_date: null,
            consecutive_days: 0,
            total_hints_earned: 0,
            total_solutions_earned: 0,
          } as DailyBonusRow;
        }

        // Convertir last_claim_date au format YYYY-MM-DD (MySQL peut retourner un objet Date)
        const lastClaimDate = bonus.last_claim_date
          ? new Date(bonus.last_claim_date)
              .toLocaleString("en-CA", {
                timeZone: "Europe/Paris",
                year: "numeric",
                month: "2-digit",
                day: "2-digit",
              })
              .split(",")[0]
          : null;

        // Vérifier si déjà réclamé aujourd'hui
        if (lastClaimDate === today) {
          await connection.rollback();
          return reply.status(400).send({
            success: false,
            error: "Bonus déjà réclamé aujourd'hui",
          });
        }

        // Calculer le nouveau streak
        let newStreak = 1;
        if (lastClaimDate === yesterday) {
          newStreak = bonus.consecutive_days + 1;
        }

        // Vérifier si bonus solution au 7ème jour
        const solutionEarned = newStreak >= STREAK_FOR_SOLUTION;
        const finalStreak = solutionEarned ? 0 : newStreak; // Reset après solution

        // Mettre à jour le bonus
        await connection.query(
          `UPDATE daily_bonus 
           SET last_claim_date = ?, 
               consecutive_days = ?,
               total_hints_earned = total_hints_earned + ?,
               total_solutions_earned = total_solutions_earned + ?
           WHERE user_id = ?`,
          [today, finalStreak, HINTS_PER_DAY, solutionEarned ? 1 : 0, userId],
        );

        // Créditer le wallet
        await connection.query(
          `UPDATE wallets 
           SET hints = hints + ?, replays = replays + ? 
           WHERE user_id = ?`,
          [HINTS_PER_DAY, solutionEarned ? 1 : 0, userId],
        );

        const dailyChanges: { resource: "hints" | "replays"; delta: number }[] =
          [{ resource: "hints", delta: HINTS_PER_DAY }];
        if (solutionEarned)
          dailyChanges.push({ resource: "replays", delta: 1 });
        await recordWalletChange(
          connection,
          userId,
          dailyChanges,
          "daily_bonus",
          { streak: newStreak, solutionEarned },
        );

        // Récupérer le wallet mis à jour
        const [walletRows] = await connection.query<RowDataPacket[]>(
          "SELECT points, hints, undos, replays FROM wallets WHERE user_id = ?",
          [userId],
        );

        await connection.commit();

        app.log.info(
          { userId, streak: newStreak, solutionEarned },
          "Bonus quotidien réclamé",
        );

        return reply.send({
          success: true,
          hintsEarned: HINTS_PER_DAY,
          solutionEarned,
          newStreak: finalStreak,
          wallet: walletRows[0] || null,
        });
      } catch (error) {
        await connection.rollback();
        app.log.error(error, "Erreur réclamation bonus quotidien");
        return reply.status(500).send({ error: "Erreur serveur" });
      } finally {
        connection.release();
      }
    },
  );

  // ========================
  // BONUS ARCADE
  // ========================

  /**
   * GET /bonus/arcade - Obtenir l'état du bonus arcade
   */
  app.get(
    "/bonus/arcade",
    { preHandler: requireAuth },
    async (request: FastifyRequest, reply: FastifyReply) => {
      const userId = request.user?.sub;
      if (!userId) {
        return reply.status(401).send({ error: "Non authentifié" });
      }

      try {
        const [rows] = await db.query<ArcadeBonusRow[]>(
          "SELECT * FROM arcade_bonus WHERE user_id = ?",
          [userId],
        );

        let bonus = rows[0];
        if (!bonus) {
          await db.query(
            "INSERT INTO arcade_bonus (user_id, total_games_played, last_bonus_at) VALUES (?, 0, 0)",
            [userId],
          );
          bonus = {
            user_id: userId,
            total_games_played: 0,
            last_bonus_at: 0,
            total_hints_earned: 0,
          } as ArcadeBonusRow;
        }

        const gamesSinceLastBonus =
          bonus.total_games_played - bonus.last_bonus_at;
        const gamesUntilBonus =
          GAMES_FOR_BONUS - (gamesSinceLastBonus % GAMES_FOR_BONUS);

        return reply.send({
          totalGamesPlayed: bonus.total_games_played,
          gamesUntilBonus:
            gamesUntilBonus === GAMES_FOR_BONUS ? 0 : gamesUntilBonus,
          progressPercent:
            ((gamesSinceLastBonus % GAMES_FOR_BONUS) / GAMES_FOR_BONUS) * 100,
          totalHintsEarned: bonus.total_hints_earned,
        });
      } catch (error) {
        app.log.error(error, "Erreur récupération bonus arcade");
        return reply.status(500).send({ error: "Erreur serveur" });
      }
    },
  );

  /**
   * POST /bonus/arcade/record - Enregistrer une partie arcade terminée
   */
  app.post(
    "/bonus/arcade/record",
    {
      preHandler: requireAuth,
      config: { rateLimit: { max: 30, timeWindow: "1 minute" } },
    },
    async (request: FastifyRequest, reply: FastifyReply) => {
      const userId = request.user?.sub;
      if (!userId) {
        return reply.status(401).send({ error: "Non authentifié" });
      }

      const connection = await db.getConnection();
      try {
        await connection.beginTransaction();

        // Récupérer ou créer l'entrée
        const [rows] = await connection.query<ArcadeBonusRow[]>(
          "SELECT * FROM arcade_bonus WHERE user_id = ? FOR UPDATE",
          [userId],
        );

        let bonus = rows[0];
        if (!bonus) {
          await connection.query(
            "INSERT INTO arcade_bonus (user_id, total_games_played, last_bonus_at) VALUES (?, 0, 0)",
            [userId],
          );
          bonus = {
            user_id: userId,
            total_games_played: 0,
            last_bonus_at: 0,
            total_hints_earned: 0,
          } as ArcadeBonusRow;
        }

        const newTotal = bonus.total_games_played + 1;

        // Vérifier si on atteint un palier de 10
        const previousMilestone = Math.floor(
          bonus.total_games_played / GAMES_FOR_BONUS,
        );
        const newMilestone = Math.floor(newTotal / GAMES_FOR_BONUS);
        const bonusEarned = newMilestone > previousMilestone;

        // Mettre à jour le bonus arcade
        await connection.query(
          `UPDATE arcade_bonus 
           SET total_games_played = ?,
               last_bonus_at = ?,
               total_hints_earned = total_hints_earned + ?
           WHERE user_id = ?`,
          [
            newTotal,
            bonusEarned ? newMilestone * GAMES_FOR_BONUS : bonus.last_bonus_at,
            bonusEarned ? HINTS_PER_ARCADE_BONUS : 0,
            userId,
          ],
        );

        // Si bonus gagné, créditer le wallet
        if (bonusEarned) {
          await connection.query(
            "UPDATE wallets SET hints = hints + ? WHERE user_id = ?",
            [HINTS_PER_ARCADE_BONUS, userId],
          );
          await recordWalletChange(
            connection,
            userId,
            [{ resource: "hints", delta: HINTS_PER_ARCADE_BONUS }],
            "arcade_bonus",
            { totalGames: newTotal },
          );
        }

        // Récupérer le wallet mis à jour
        const [walletRows] = await connection.query<RowDataPacket[]>(
          "SELECT points, hints, undos, replays FROM wallets WHERE user_id = ?",
          [userId],
        );

        await connection.commit();

        const gamesUntilNextBonus =
          GAMES_FOR_BONUS - (newTotal % GAMES_FOR_BONUS);

        if (bonusEarned) {
          app.log.info({ userId, totalGames: newTotal }, "Bonus arcade gagné");
        }

        return reply.send({
          success: true,
          bonusEarned,
          hintsEarned: bonusEarned ? HINTS_PER_ARCADE_BONUS : 0,
          totalGamesPlayed: newTotal,
          gamesUntilNextBonus:
            gamesUntilNextBonus === GAMES_FOR_BONUS ? 0 : gamesUntilNextBonus,
          wallet: walletRows[0] || null,
        });
      } catch (error) {
        await connection.rollback();
        app.log.error(error, "Erreur enregistrement partie arcade");
        return reply.status(500).send({ error: "Erreur serveur" });
      } finally {
        connection.release();
      }
    },
  );
};
