import type { FastifyInstance } from "fastify";
import type { Pool } from "mysql2/promise";
import type { RouteContext } from "./types.js";
import { recordAudit } from "../server/admin-service.js";

const paramsUserIdSchema = {
  type: "object" as const,
  required: ["userId"],
  properties: { userId: { type: "string", pattern: "^[0-9]+$" } },
};

/**
 * Représente un pack d'avatars disponible à l'achat
 */
interface AvatarPack {
  id: number;
  packId: string;
  name: string;
  description: string | null;
  price: number;
  folderPath: string;
  avatarCount: number;
  isActive: boolean;
}

/**
 * Récupère un pack d'avatars par son ID
 */
const getAvatarPackById = async (
  db: Pool,
  packId: string,
): Promise<AvatarPack | null> => {
  const [rows] = await db.execute(
    `SELECT id, pack_id, name, description, price, folder_path, avatar_count, is_active
     FROM avatar_packs WHERE pack_id = ? AND is_active = 1 LIMIT 1`,
    [packId],
  );
  if (!Array.isArray(rows) || rows.length === 0) return null;

  const row = rows[0] as Record<string, unknown>;
  return {
    id: row.id as number,
    packId: row.pack_id as string,
    name: row.name as string,
    description: row.description as string | null,
    price: Number(row.price),
    folderPath: row.folder_path as string,
    avatarCount: row.avatar_count as number,
    isActive: Boolean(row.is_active),
  };
};

/**
 * Récupère tous les packs d'avatars actifs
 */
const getAllActivePacks = async (db: Pool): Promise<AvatarPack[]> => {
  const [rows] = await db.execute(
    `SELECT id, pack_id, name, description, price, folder_path, avatar_count, is_active
     FROM avatar_packs WHERE is_active = 1 ORDER BY price ASC`,
  );
  if (!Array.isArray(rows)) return [];

  return rows.map((row) => {
    const item = row as Record<string, unknown>;
    return {
      id: item.id as number,
      packId: item.pack_id as string,
      name: item.name as string,
      description: item.description as string | null,
      price: Number(item.price),
      folderPath: item.folder_path as string,
      avatarCount: item.avatar_count as number,
      isActive: Boolean(item.is_active),
    };
  });
};

/**
 * Vérifie si un utilisateur possède un pack d'avatars
 */
const userOwnsAvatarPack = async (
  db: Pool,
  userId: number,
  packId: string,
): Promise<boolean> => {
  const [rows] = await db.execute(
    "SELECT 1 FROM user_avatar_packs WHERE user_id = ? AND pack_id = ? LIMIT 1",
    [userId, packId],
  );
  return Array.isArray(rows) && rows.length > 0;
};

/**
 * Récupère tous les packs d'avatars possédés par un utilisateur
 */
const getUserOwnedPacks = async (
  db: Pool,
  userId: number,
): Promise<{ packId: string; purchasedAt: Date }[]> => {
  const [rows] = await db.execute(
    `SELECT pack_id, purchased_at FROM user_avatar_packs WHERE user_id = ?`,
    [userId],
  );
  if (!Array.isArray(rows)) return [];

  return rows.map((row) => {
    const item = row as Record<string, unknown>;
    return {
      packId: item.pack_id as string,
      purchasedAt: new Date(item.purchased_at as string),
    };
  });
};

/**
 * Liste les fichiers d'avatars pour un pack (basé sur le folder_path)
 */
const getAvatarFilesForPack = (packId: string): string[] => {
  // Pour le pack "cool", on retourne les 19 avatars (01.png à 19.png)
  if (packId === "cool") {
    return Array.from({ length: 19 }, (_, i) => {
      const num = (i + 1).toString().padStart(2, "0");
      return `${num}.png`;
    });
  }
  return [];
};

export const registerAvatarPackRoutes = (
  app: FastifyInstance,
  ctx: RouteContext,
) => {
  const { db, requireAuth } = ctx;

  // GET /avatar-packs - Liste tous les packs d'avatars disponibles
  app.get("/avatar-packs", { preHandler: requireAuth }, async (request) => {
    const userId = request.user?.sub;
    const packs = await getAllActivePacks(db);

    // Pour chaque pack, vérifier si l'utilisateur le possède
    const packsWithOwnership = await Promise.all(
      packs.map(async (pack) => {
        const owned = userId
          ? await userOwnsAvatarPack(db, userId, pack.packId)
          : false;
        return {
          ...pack,
          priceDisplay: `${pack.price.toFixed(2)} €`,
          owned,
          avatarFiles: getAvatarFilesForPack(pack.packId),
        };
      }),
    );

    return { packs: packsWithOwnership };
  });

  // GET /avatar-packs/:packId - Détails d'un pack d'avatars
  app.get(
    "/avatar-packs/:packId",
    { preHandler: requireAuth },
    async (request, reply) => {
      const packId = (request.params as { packId: string }).packId;
      const userId = request.user?.sub;

      const pack = await getAvatarPackById(db, packId);
      if (!pack) {
        reply.code(404);
        return { error: "Pack introuvable" };
      }

      const owned = userId
        ? await userOwnsAvatarPack(db, userId, packId)
        : false;

      return {
        pack: {
          ...pack,
          priceDisplay: `${pack.price.toFixed(2)} €`,
          owned,
          avatarFiles: getAvatarFilesForPack(packId),
        },
      };
    },
  );

  // GET /avatar-packs/user/:userId - Liste les packs possédés par un utilisateur
  app.get(
    "/avatar-packs/user/: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é" };
      }

      const ownedPacks = await getUserOwnedPacks(db, userId);

      // Récupérer les détails de chaque pack possédé
      const packsWithDetails = await Promise.all(
        ownedPacks.map(async (owned) => {
          const pack = await getAvatarPackById(db, owned.packId);
          if (!pack) return null;
          return {
            ...pack,
            purchasedAt: owned.purchasedAt.toISOString(),
            avatarFiles: getAvatarFilesForPack(owned.packId),
          };
        }),
      );

      return {
        ownedPacks: packsWithDetails.filter(Boolean),
      };
    },
  );

  // POST /avatar-packs/purchase/:userId - Acheter un pack d'avatars
  app.post(
    "/avatar-packs/purchase/:userId",
    {
      preHandler: requireAuth,
      schema: {
        params: paramsUserIdSchema,
        body: {
          type: "object",
          required: ["packId"],
          properties: {
            packId: { type: "string" },
            transactionId: { type: "string" },
          },
        },
      },
    },
    async (request, reply) => {
      const userId = Number((request.params as { userId: string }).userId);
      const tokenUserId = request.user?.sub;
      const body = request.body as {
        packId: string;
        transactionId?: string;
      };

      if (!Number.isFinite(userId)) {
        reply.code(400);
        return { error: "userId invalide" };
      }
      if (!tokenUserId || tokenUserId !== userId) {
        reply.code(403);
        return { error: "Accès refusé" };
      }

      // ✅ SÉCURISÉ: Vérifier que transactionId est fourni et valide
      if (
        !body?.transactionId ||
        typeof body.transactionId !== "string" ||
        body.transactionId.trim().length === 0
      ) {
        reply.code(400);
        return { error: "transactionId invalide (requis pour le paiement)" };
      }

      // Vérifier que le pack existe et est actif
      const pack = await getAvatarPackById(db, body.packId);
      if (!pack) {
        reply.code(404);
        return { error: "Pack introuvable ou non disponible" };
      }

      // ✅ SÉCURISÉ: Essayer d'insérer et gérer les doublons via contrainte unique
      // Cela prévient les race conditions: si l'utilisateur possède déjà le pack,
      // la base de données rejettera l'insertion (UNIQUE constraint)
      try {
        await db.execute(
          `INSERT INTO user_avatar_packs (user_id, pack_id, transaction_id, price_paid, purchased_at)
           VALUES (?, ?, ?, ?, NOW())`,
          [userId, body.packId, body.transactionId.trim(), pack.price],
        );
      } catch (err: any) {
        // Si c'est une violation de contrainte unique (pack déjà possédé)
        if (err.code === "ER_DUP_ENTRY" || err.code === 1062) {
          reply.code(400);
          return { error: "Vous possédez déjà ce pack" };
        }
        throw err;
      }

      await recordAudit(db, {
        userId,
        action: "avatar_pack.purchase",
        meta: {
          packId: body.packId,
          packName: pack.name,
          price: pack.price,
          transactionId: body.transactionId,
          avatarCount: pack.avatarCount,
        },
        ip: request.ip,
        userAgent: request.headers["user-agent"]?.toString() ?? null,
        installationId:
          request.headers["x-installation-id"]?.toString() ?? null,
      });

      return {
        success: true,
        message: `Pack "${pack.name}" débloqué ! 🎉 ${pack.avatarCount} avatars sont maintenant disponibles !`,
        pack: {
          ...pack,
          priceDisplay: `${pack.price.toFixed(2)} €`,
          owned: true,
          avatarFiles: getAvatarFilesForPack(body.packId),
        },
      };
    },
  );
};
