import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import type { Pool } from "mysql2/promise";
import type { RouteContext } from "./types.js";
import { parseJsonNumberArray } from "../server/parsers.js";
import { recordAudit, recordWalletChange } from "../server/admin-service.js";

const paramsUserIdSchema = {
  type: "object" as const,
  required: ["userId"],
  properties: { userId: { type: "string", pattern: "^[0-9]+$" } },
};

// Constantes
const TOTAL_ANIMAL_AVATARS = 380;
const DUPLICATE_AVATAR_POINTS = 1000;

// Utilitaire: calculer 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")}`;
};

// Utilitaire: obtenir le jour de la semaine (1=lundi, 7=dimanche)
const getDayOfWeek = (date: Date = new Date()): number => {
  const day = date.getDay();
  return day === 0 ? 7 : day;
};

// Assurer que la table weekly_progress existe pour l'utilisateur
const ensureWeeklyProgress = async (
  db: Pool,
  userId: number,
  weekKey: string,
) => {
  await db.execute(
    `INSERT IGNORE INTO weekly_progress (user_id, week_key, completed_days, reward_claimed)
     VALUES (?, ?, '[]', 0)`,
    [userId, weekKey],
  );
};

// Obtenir ou créer la récompense de la semaine
const getOrCreateWeeklyReward = async (
  db: Pool,
  weekKey: string,
): Promise<{ avatarId: number; avatarPath: string }> => {
  const [existingRows] = await db.execute(
    "SELECT avatar_id FROM weekly_rewards WHERE week_key = ?",
    [weekKey],
  );
  const existing = Array.isArray(existingRows) ? existingRows[0] : undefined;
  if (existing && (existing as { avatar_id?: number }).avatar_id) {
    const avatarId = (existing as { avatar_id: number }).avatar_id;
    return {
      avatarId,
      // Preview sécurisé (visible pour tout joueur authentifié, uniquement pour l'avatar de la semaine)
      avatarPath: `/api/avatars/weekly-reward/${avatarId}/image`,
    };
  }

  const [usedRows] = await db.execute(
    "SELECT avatar_id FROM used_weekly_avatars",
  );
  const usedAvatars = new Set<number>();
  if (Array.isArray(usedRows)) {
    usedRows.forEach((row) => {
      const avatarId = (row as { avatar_id?: number }).avatar_id;
      if (avatarId) usedAvatars.add(avatarId);
    });
  }

  if (usedAvatars.size >= TOTAL_ANIMAL_AVATARS) {
    await db.execute("DELETE FROM used_weekly_avatars");
    usedAvatars.clear();
  }

  const availableAvatars: number[] = [];
  for (let i = 1; i <= TOTAL_ANIMAL_AVATARS; i++) {
    if (!usedAvatars.has(i)) {
      availableAvatars.push(i);
    }
  }
  const randomIndex = Math.floor(Math.random() * availableAvatars.length);
  const avatarId = availableAvatars[randomIndex];

  await db.execute(
    "INSERT INTO weekly_rewards (week_key, avatar_id) VALUES (?, ?)",
    [weekKey, avatarId],
  );
  await db.execute(
    "INSERT IGNORE INTO used_weekly_avatars (avatar_id) VALUES (?)",
    [avatarId],
  );

  return {
    avatarId,
    // Preview sécurisé (visible pour tout joueur authentifié, uniquement pour l'avatar de la semaine)
    avatarPath: `/api/avatars/weekly-reward/${avatarId}/image`,
  };
};

// Récupérer la progression hebdomadaire
const fetchWeeklyProgress = async (db: Pool, userId: number) => {
  const weekKey = getWeekKey();
  await ensureWeeklyProgress(db, userId, weekKey);

  const [rows] = await db.execute(
    `SELECT week_key, completed_days, reward_claimed, reward_type
     FROM weekly_progress WHERE user_id = ? AND week_key = ?`,
    [userId, weekKey],
  );
  const row = Array.isArray(rows) ? rows[0] : undefined;
  const item = row as
    | {
        week_key?: string;
        completed_days?: string;
        reward_claimed?: number;
        reward_type?: string;
      }
    | undefined;

  return {
    weekKey,
    completedDays: parseJsonNumberArray(item?.completed_days),
    rewardClaimed: Boolean(item?.reward_claimed),
    rewardType: item?.reward_type as "avatar" | "points" | undefined,
  };
};

// Vérifier si l'utilisateur possède déjà un avatar
const userOwnsAvatar = async (
  db: Pool,
  userId: number,
  avatarId: number,
): Promise<boolean> => {
  const [rows] = await db.execute(
    "SELECT 1 FROM user_weekly_avatars WHERE user_id = ? AND avatar_id = ?",
    [userId, avatarId],
  );
  return Array.isArray(rows) && rows.length > 0;
};

export const registerWeeklyRoutes = (
  app: FastifyInstance,
  ctx: RouteContext,
) => {
  const { db, requireAuth } = ctx;

  // GET /weekly/:userId
  app.get(
    "/weekly/: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 weekKey = getWeekKey();
      const progress = await fetchWeeklyProgress(db, userId);
      const reward = await getOrCreateWeeklyReward(db, weekKey);
      const today = getDayOfWeek();

      return {
        progress,
        reward: {
          weekKey,
          avatarId: reward.avatarId,
          avatarPath: reward.avatarPath,
        },
        todayDayOfWeek: today,
      };
    },
  );

  // POST /weekly/:userId/complete
  app.post(
    "/weekly/:userId/complete",
    {
      preHandler: requireAuth,
      config: {
        rateLimit: {
          max: 30,
          timeWindow: "1 minute",
        },
      },
      schema: {
        params: paramsUserIdSchema,
        body: {
          type: "object",
          required: ["dayOfWeek"],
          additionalProperties: false,
          properties: {
            dayOfWeek: { type: "number", minimum: 1, maximum: 7 },
          },
        },
      },
    },
    async (request, reply) => {
      const userId = Number((request.params as { userId: string }).userId);
      const body = request.body as { dayOfWeek?: number } | undefined;
      const tokenUserId = request.user?.sub;
      const dayOfWeek = body?.dayOfWeek;

      if (!Number.isFinite(userId)) {
        reply.code(400);
        return { error: "userId invalide" };
      }
      if (!tokenUserId || tokenUserId !== userId) {
        reply.code(403);
        return { error: "Acces refuse" };
      }
      if (!dayOfWeek || dayOfWeek < 1 || dayOfWeek > 7) {
        reply.code(400);
        return { error: "dayOfWeek invalide (1-7)" };
      }

      const today = getDayOfWeek();
      if (dayOfWeek > today) {
        reply.code(400);
        return { error: "Ce défi n'est pas encore disponible" };
      }

      const weekKey = getWeekKey();
      const current = await fetchWeeklyProgress(db, userId);

      if (!current.completedDays.includes(dayOfWeek)) {
        const next = [...current.completedDays, dayOfWeek].sort(
          (a, b) => a - b,
        );
        await db.execute(
          "UPDATE weekly_progress SET completed_days = ? WHERE user_id = ? AND week_key = ?",
          [JSON.stringify(next), userId, weekKey],
        );
      }

      await recordAudit(db, {
        userId,
        action: "weekly.complete",
        meta: { weekKey, dayOfWeek },
        ip: request.ip,
        userAgent: request.headers["user-agent"]?.toString() ?? null,
        installationId:
          request.headers["x-installation-id"]?.toString() ?? null,
      });

      const progress = await fetchWeeklyProgress(db, userId);
      const reward = await getOrCreateWeeklyReward(db, weekKey);

      return {
        progress,
        reward: {
          weekKey,
          avatarId: reward.avatarId,
          avatarPath: reward.avatarPath,
        },
      };
    },
  );

  // POST /weekly/:userId/claim
  app.post(
    "/weekly/:userId/claim",
    {
      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: "Acces refuse" };
      }

      const weekKey = getWeekKey();
      const progress = await fetchWeeklyProgress(db, userId);

      if (progress.completedDays.length < 7) {
        reply.code(400);
        return {
          error: `Il faut compléter les 7 jours (${progress.completedDays.length}/7)`,
        };
      }

      if (progress.rewardClaimed) {
        reply.code(400);
        return { error: "Récompense déjà réclamée cette semaine" };
      }

      const reward = await getOrCreateWeeklyReward(db, weekKey);
      const alreadyOwns = await userOwnsAvatar(db, userId, reward.avatarId);

      let rewardType: "avatar" | "points";
      let pointsAwarded = 0;

      if (alreadyOwns) {
        rewardType = "points";
        pointsAwarded = DUPLICATE_AVATAR_POINTS;
        await db.execute(
          "UPDATE wallets SET points = points + ? WHERE user_id = ?",
          [DUPLICATE_AVATAR_POINTS, userId],
        );
        await recordWalletChange(
          db,
          userId,
          [{ resource: "points", delta: DUPLICATE_AVATAR_POINTS }],
          "weekly.claim",
          { weekKey, avatarId: reward.avatarId },
        );
      } else {
        rewardType = "avatar";
        await db.execute(
          `INSERT INTO user_weekly_avatars (user_id, avatar_id, obtained_from)
           VALUES (?, ?, ?)`,
          [userId, reward.avatarId, weekKey],
        );
      }

      await db.execute(
        "UPDATE weekly_progress SET reward_claimed = 1, reward_type = ? WHERE user_id = ? AND week_key = ?",
        [rewardType, userId, weekKey],
      );

      await recordAudit(db, {
        userId,
        action: "weekly.claim",
        meta: { weekKey, rewardType, avatarId: reward.avatarId, pointsAwarded },
        ip: request.ip,
        userAgent: request.headers["user-agent"]?.toString() ?? null,
        installationId:
          request.headers["x-installation-id"]?.toString() ?? null,
      });

      const updatedProgress = await fetchWeeklyProgress(db, userId);

      return {
        success: true,
        rewardType,
        avatarId: reward.avatarId,
        points: rewardType === "points" ? DUPLICATE_AVATAR_POINTS : 0,
        progress: updatedProgress,
      };
    },
  );

  // GET /weekly/:userId/avatars
  app.get(
    "/weekly/:userId/avatars",
    {
      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" };
      }

      // Récupérer les avatars gagnés depuis user_weekly_avatars
      // (pas de FK vers avatars, donc peut stocker les avatars animal 1-380)
      const [rows] = await db.execute(
        `SELECT avatar_id, obtained_from, obtained_at, week_key
         FROM user_weekly_avatars
         WHERE user_id = ?`,
        [userId],
      );

      const avatars = Array.isArray(rows)
        ? rows.map((row) => {
            const item = row as {
              avatar_id: number;
              obtained_from: string | null;
              obtained_at: Date;
              week_key: string | null;
            };

            // Les avatars gagnés sont toujours des avatars animal (1-380)
            // Avatars weekly (animaux): servis via route sécurisée (l'utilisateur les possède)
            const avatarPath = `/api/avatars/${item.avatar_id}/image`;

            return {
              avatarId: item.avatar_id,
              avatarCode: null,
              avatarPath,
              category: "animal",
              obtainedAt: item.obtained_at,
              obtainedFrom: item.obtained_from ?? "weekly",
              weekKey: item.week_key,
            };
          })
        : [];

      // Inclure aussi les avatars "récompense" débloqués (ex: beta-testeur-01)
      const [rewardRows] = await db.execute(
        `SELECT a.id, a.code, ua.unlocked_at as obtainedAt, ua.source as obtainedFrom
         FROM user_avatars ua
         JOIN avatars a ON a.id = ua.avatar_id
         WHERE ua.user_id = ? AND a.code LIKE 'beta-testeur%'
         ORDER BY ua.unlocked_at DESC`,
        [userId],
      );

      const rewardAvatars = Array.isArray(rewardRows)
        ? rewardRows.map((row) => {
            const item = row as {
              id?: number;
              code?: string;
              obtainedAt?: Date;
              obtainedFrom?: string | null;
            };
            const code = item.code ?? "";
            const avatarPath = code
              ? `/api/avatars/${encodeURIComponent(code)}/image`
              : "";
            return {
              avatarId: Number(item.id) || 0,
              avatarCode: code || null,
              avatarPath,
              category: "recompense",
              obtainedAt: item.obtainedAt ?? new Date(),
              obtainedFrom: item.obtainedFrom ?? "reward",
              weekKey: null,
            };
          })
        : [];

      return { avatars: [...avatars, ...rewardAvatars] };
    },
  );
};
