import type { FastifyInstance } from "fastify";
import type { Pool } from "mysql2/promise";
import type { RouteContext } from "./types.js";
import { recordAudit } from "../server/admin-service.js";
import { parseLevelPayload } from "../server/parsers.js";

const CATALOG_LEVELS = 200;
const CHALLENGE_PACK_PRICE = 4.99;

const paramsUserIdSchema = {
  type: "object" as const,
  required: ["userId"],
  properties: { userId: { type: "string", pattern: "^[0-9]+$" } },
};

/**
 * Vérifie si un utilisateur possède le pack défis
 */
const userOwnsChallengePack = async (
  db: Pool,
  userId: number,
): Promise<boolean> => {
  const [rows] = await db.execute(
    "SELECT 1 FROM user_challenge_pack WHERE user_id = ? LIMIT 1",
    [userId],
  );
  return Array.isArray(rows) && rows.length > 0;
};

/**
 * Vérifie si le pack défis est activé dans la config
 */
const isChallengePackEnabled = async (db: Pool): Promise<boolean> => {
  const [rows] = await db.execute(
    "SELECT config_value FROM app_config WHERE config_key = 'challenge_pack_enabled' LIMIT 1",
  );
  const row = Array.isArray(rows)
    ? (rows[0] as { config_value?: string })
    : undefined;
  const value = row?.config_value?.trim().toLowerCase();
  return value === "1" || value === "true";
};

/**
 * Récupère les avatars des semaines passées que l'utilisateur ne possède pas encore
 */
const getPastWeeklyAvatars = async (
  db: Pool,
  userId: number,
): Promise<{ avatarId: number; weekKey: string }[]> => {
  // Récupérer tous les avatars des semaines passées
  const currentWeekKey = getWeekKey();
  const [rows] = await db.execute(
    `SELECT wr.week_key, wr.avatar_id
     FROM weekly_rewards wr
     WHERE wr.week_key < ?
     AND wr.avatar_id NOT IN (
       SELECT avatar_id FROM user_weekly_avatars WHERE user_id = ?
     )
     ORDER BY wr.week_key ASC`,
    [currentWeekKey, userId],
  );

  if (!Array.isArray(rows)) return [];
  return rows.map((row) => {
    const item = row as { week_key?: string; avatar_id?: number };
    return {
      weekKey: item.week_key ?? "",
      avatarId: item.avatar_id ?? 0,
    };
  });
};

/**
 * Compte le nombre total d'avatars passés disponibles
 */
const countPastWeeklyAvatars = async (db: Pool): Promise<number> => {
  const currentWeekKey = getWeekKey();
  const [rows] = await db.execute(
    "SELECT COUNT(*) as count FROM weekly_rewards WHERE week_key < ?",
    [currentWeekKey],
  );
  const row = Array.isArray(rows) ? (rows[0] as { count?: number }) : undefined;
  return row?.count ?? 0;
};

/**
 * Calcule la clé de semaine ISO (YYYY-WXX)
 */
const getWeekKey = (date: Date = new Date()): string => {
  const d = new Date(date);
  d.setHours(0, 0, 0, 0);
  d.setDate(d.getDate() + 3 - ((d.getDay() + 6) % 7));
  const week1 = new Date(d.getFullYear(), 0, 4);
  const weekNumber =
    Math.round(
      ((d.getTime() - week1.getTime()) / 86400000 -
        3 +
        ((week1.getDay() + 6) % 7)) /
        7,
    ) + 1;
  return `${d.getFullYear()}-W${String(weekNumber).padStart(2, "0")}`;
};

/**
 * Calcule le jour de l'année (1-366)
 */
const getDayOfYear = (date: Date): number => {
  const start = new Date(date.getFullYear(), 0, 0);
  const diff = date.getTime() - start.getTime();
  const oneDay = 86400000;
  return Math.floor(diff / oneDay);
};

export const registerChallengePackRoutes = (
  app: FastifyInstance,
  ctx: RouteContext,
) => {
  const { db, requireAuth } = ctx;

  // GET /challenge-pack/status - Vérifie si le pack est activé et si l'utilisateur le possède
  app.get(
    "/challenge-pack/status",
    { preHandler: requireAuth },
    async (request) => {
      const userId = request.user?.sub;
      const enabled = await isChallengePackEnabled(db);
      const owned = userId ? await userOwnsChallengePack(db, userId) : false;

      // Compter le nombre d'avatars passés disponibles
      const totalPastAvatars = await countPastWeeklyAvatars(db);

      // Si l'utilisateur est connecté, compter combien il pourrait obtenir
      let avatarsToUnlock = 0;
      if (userId && !owned) {
        const pastAvatars = await getPastWeeklyAvatars(db, userId);
        avatarsToUnlock = pastAvatars.length;
      }

      return {
        enabled,
        owned,
        price: CHALLENGE_PACK_PRICE,
        priceDisplay: `${CHALLENGE_PACK_PRICE.toFixed(2)} €`,
        totalPastAvatars,
        avatarsToUnlock,
      };
    },
  );

  // POST /challenge-pack/purchase - Acheter le pack défis
  // ⚠️ SÉCURISÉ: cette route ne fait que renvoyer vers un checkout Stripe.
  // La livraison du pack se fait UNIQUEMENT via le webhook Stripe (packType=challenge).
  app.post(
    "/challenge-pack/purchase/:userId",
    {
      preHandler: requireAuth,
      config: {
        rateLimit: {
          max: 10,
          timeWindow: "1 minute",
        },
      },
      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: "Accès refusé" };
      }

      // Vérifier si le pack est activé
      const enabled = await isChallengePackEnabled(db);
      if (!enabled) {
        reply.code(400);
        return { error: "Le pack défis n'est pas disponible actuellement" };
      }

      // Vérifier si l'utilisateur possède déjà le pack
      const alreadyOwned = await userOwnsChallengePack(db, userId);
      if (alreadyOwned) {
        reply.code(400);
        return { error: "Vous possédez déjà ce pack" };
      }

      // Rediriger vers le checkout Stripe pour le paiement réel
      // Le pack sera livré automatiquement via le webhook Stripe (packType=challenge)
      return {
        requiresPayment: true,
        price: CHALLENGE_PACK_PRICE,
        priceDisplay: `${CHALLENGE_PACK_PRICE.toFixed(2)} €`,
        checkoutEndpoint: "/api/payments/stripe/checkout-challenge-pack",
        message: "Redirection vers le paiement...",
      };
    },
  );

  // GET /challenge-pack/challenges - Liste des défis passés disponibles
  app.get(
    "/challenge-pack/challenges/: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: "Accès refusé" };
      }

      // Vérifier si l'utilisateur possède le pack
      const owned = await userOwnsChallengePack(db, userId);
      if (!owned) {
        reply.code(403);
        return { error: "Pack défis requis pour accéder aux défis passés" };
      }

      // Récupérer les défis disponibles (tous les défis avant aujourd'hui)
      const today = new Date();
      today.setHours(0, 0, 0, 0);
      const todayStr = today.toISOString().split("T")[0];

      const [rows] = await db.execute(
        `SELECT date_key, difficulty, level_index 
         FROM daily_challenges 
         WHERE date_key < ?
         ORDER BY date_key DESC`,
        [todayStr],
      );

      // Récupérer les défis déjà complétés par l'utilisateur
      const [completedRows] = await db.execute(
        `SELECT DISTINCT JSON_UNQUOTE(JSON_EXTRACT(meta, '$.dateKey')) as date_key
         FROM audit_log 
         WHERE user_id = ? AND action = 'daily.complete'`,
        [userId],
      );
      const completedSet = new Set<string>();
      if (Array.isArray(completedRows)) {
        completedRows.forEach((row) => {
          const dateKey = (row as { date_key?: string }).date_key;
          if (dateKey) completedSet.add(dateKey);
        });
      }

      const challenges = (Array.isArray(rows) ? rows : []).map((row) => {
        const item = row as {
          date_key?: Date;
          difficulty?: string;
          level_index?: number;
        };
        const dateKey =
          item.date_key instanceof Date
            ? item.date_key.toISOString().split("T")[0]
            : String(item.date_key);
        return {
          dateKey,
          difficulty: item.difficulty ?? "medium",
          levelIndex: item.level_index ?? 0,
          completed: completedSet.has(dateKey),
        };
      });

      return { challenges };
    },
  );

  // GET /challenge-pack/level/:dateKey - Charger un niveau de défi passé
  app.get(
    "/challenge-pack/level/:dateKey",
    {
      preHandler: requireAuth,
      schema: {
        params: {
          type: "object",
          required: ["dateKey"],
          properties: {
            dateKey: { type: "string", minLength: 8, maxLength: 16 },
          },
        },
      },
    },
    async (request, reply) => {
      const userId = request.user?.sub;
      const params = request.params as { dateKey: string };
      const dateKey = params.dateKey.trim();

      if (!userId) {
        reply.code(401);
        return { error: "Authentification requise" };
      }

      // Vérifier si l'utilisateur possède le pack
      const owned = await userOwnsChallengePack(db, userId);
      if (!owned) {
        reply.code(403);
        return { error: "Pack défis requis" };
      }

      // Vérifier que la date est bien passée
      const parsed = new Date(dateKey);
      if (Number.isNaN(parsed.getTime())) {
        reply.code(400);
        return { error: "Date invalide" };
      }
      const today = new Date();
      today.setHours(0, 0, 0, 0);
      if (parsed >= today) {
        reply.code(400);
        return {
          error: "Ce défi n'est pas encore disponible dans les archives",
        };
      }

      // Chercher dans la table des défis ou générer dynamiquement
      const [challengeRows] = await db.execute(
        "SELECT level_index, difficulty FROM daily_challenges WHERE date_key = ? LIMIT 1",
        [dateKey],
      );

      let levelIndex: number;
      let difficulty = "medium";

      const challengeRow = Array.isArray(challengeRows)
        ? (challengeRows[0] as { level_index?: number; difficulty?: string })
        : undefined;

      if (challengeRow?.level_index !== undefined) {
        levelIndex = challengeRow.level_index;
        difficulty = challengeRow.difficulty ?? "medium";
      } else {
        // Génération dynamique basée sur le jour de l'année
        levelIndex = getDayOfYear(parsed) % CATALOG_LEVELS;
      }

      // Récupérer le niveau du catalogue
      const [rows] = await db.execute(
        "SELECT payload FROM levels_catalog WHERE difficulty = ? AND level_index = ? LIMIT 1",
        [difficulty, levelIndex],
      );
      const row = Array.isArray(rows)
        ? (rows[0] as { payload?: unknown })
        : undefined;
      const payload = parseLevelPayload(row?.payload);

      if (!payload) {
        reply.code(404);
        return { error: "Niveau introuvable" };
      }

      const level = { ...payload, name: `Défi ${dateKey}` } as Record<
        string,
        unknown
      >;
      delete level.moveLimit;
      delete level.timeLimit;

      return { level };
    },
  );
};
