import { prisma } from "../database";
import type { User, Role, Prisma } from "@/prisma/generated/prisma/client";
import { NotFoundError } from "../errors";
import { userService } from "./user.service";

export interface UpdateUserStatsInput {
  hpDelta?: number;
  xpDelta?: number;
  score?: number;
  level?: number;
  role?: Role;
}

export interface LeaderboardEntry {
  rank: number;
  username: string;
  email: string;
  score: number;
  level: number;
  avatar: string | null;
  bio: string | null;
  characterClass: string | null;
}

/**
 * Service de gestion des statistiques utilisateur
 */
export class UserStatsService {
  /**
   * Récupère le leaderboard
   */
  async getLeaderboard(limit: number = 10): Promise<LeaderboardEntry[]> {
    const users = await prisma.user.findMany({
      orderBy: {
        score: "desc",
      },
      take: limit,
      select: {
        id: true,
        username: true,
        email: true,
        score: true,
        level: true,
        avatar: true,
        bio: true,
        characterClass: true,
      },
    });

    return users.map((user, index) => ({
      rank: index + 1,
      username: user.username,
      email: user.email,
      score: user.score,
      level: user.level,
      avatar: user.avatar,
      bio: user.bio,
      characterClass: user.characterClass,
    }));
  }

  /**
   * Met à jour les statistiques d'un utilisateur
   */
  async updateUserStats(
    id: string,
    stats: UpdateUserStatsInput,
    select?: Prisma.UserSelect
  ): Promise<User> {
    // Récupérer l'utilisateur actuel
    const user = await prisma.user.findUnique({
      where: { id },
    });

    if (!user) {
      throw new Error("Utilisateur non trouvé");
    }

    // Calculer les nouvelles valeurs
    let newHp = user.hp;
    let newXp = user.xp;
    let newLevel = user.level;
    let newMaxXp = user.maxXp;

    // Appliquer les changements de HP
    if (stats.hpDelta !== undefined) {
      newHp = Math.max(0, Math.min(user.maxHp, user.hp + stats.hpDelta));
    }

    // Appliquer les changements de XP
    if (stats.xpDelta !== undefined) {
      newXp = user.xp + stats.xpDelta;
      newLevel = user.level;
      newMaxXp = user.maxXp;

      // Gérer le niveau up si nécessaire (quand on ajoute de l'XP)
      if (newXp >= newMaxXp && newXp > 0) {
        while (newXp >= newMaxXp) {
          newXp -= newMaxXp;
          newLevel += 1;
          // Augmenter le maxXp pour le prochain niveau (formule simple)
          newMaxXp = Math.floor(newMaxXp * 1.2);
        }
      }

      // Gérer le niveau down si nécessaire (quand on enlève de l'XP)
      if (newXp < 0 && newLevel > 1) {
        while (newXp < 0 && newLevel > 1) {
          newLevel -= 1;
          // Calculer le maxXp du niveau précédent
          newMaxXp = Math.floor(newMaxXp / 1.2);
          newXp += newMaxXp;
        }
        // S'assurer que l'XP ne peut pas être négative
        newXp = Math.max(0, newXp);
      }

      // S'assurer que le niveau minimum est 1
      if (newLevel < 1) {
        newLevel = 1;
        newXp = 0;
      }
    }

    // Construire les données de mise à jour
    const updateData: Prisma.UserUpdateInput = {
      hp: newHp,
      xp: newXp,
      level: newLevel,
      maxXp: newMaxXp,
    };

    // Appliquer les changements directs
    if (stats.score !== undefined) {
      updateData.score = Math.max(0, stats.score);
    }

    if (stats.level !== undefined) {
      // Si le niveau est modifié directement, utiliser cette valeur
      const targetLevel = Math.max(1, stats.level);
      updateData.level = targetLevel;

      // Recalculer le maxXp pour le nouveau niveau
      // Formule: maxXp = 5000 * (1.2 ^ (level - 1))
      let calculatedMaxXp = 5000;
      for (let i = 1; i < targetLevel; i++) {
        calculatedMaxXp = Math.floor(calculatedMaxXp * 1.2);
      }
      updateData.maxXp = calculatedMaxXp;

      // Réinitialiser l'XP si le niveau change directement (sauf si on modifie aussi l'XP)
      if (targetLevel !== user.level && stats.xpDelta === undefined) {
        updateData.xp = 0;
      }
    }

    if (stats.role !== undefined) {
      if (stats.role === "ADMIN" || stats.role === "USER") {
        updateData.role = stats.role;
      }
    }

    return prisma.user.update({
      where: { id },
      data: updateData,
      select,
    });
  }

  /**
   * Met à jour les stats ET le profil d'un utilisateur (pour admin)
   */
  async updateUserStatsAndProfile(
    id: string,
    data: {
      username?: string;
      avatar?: string | null;
      hpDelta?: number;
      xpDelta?: number;
      score?: number;
      level?: number;
      role?: Role;
    },
    select?: Prisma.UserSelect
  ): Promise<User> {
    // Vérifier que l'utilisateur existe
    const user = await userService.getUserById(id);
    if (!user) {
      throw new NotFoundError("Utilisateur");
    }

    const selectFields = select || {
      id: true,
      username: true,
      email: true,
      role: true,
      score: true,
      level: true,
      hp: true,
      maxHp: true,
      xp: true,
      maxXp: true,
      avatar: true,
    };

    // Mettre à jour les stats si nécessaire
    const hasStatsChanges =
      data.hpDelta !== undefined ||
      data.xpDelta !== undefined ||
      data.score !== undefined ||
      data.level !== undefined ||
      data.role !== undefined;

    let updatedUser: User;
    if (hasStatsChanges) {
      updatedUser = await this.updateUserStats(
        id,
        {
          hpDelta: data.hpDelta,
          xpDelta: data.xpDelta,
          score: data.score,
          level: data.level,
          role: data.role,
        },
        selectFields
      );
    } else {
      updatedUser = await userService.getUserById(id, selectFields);
      if (!updatedUser) {
        throw new NotFoundError("Utilisateur");
      }
    }

    // Mettre à jour username/avatar si nécessaire
    if (data.username !== undefined || data.avatar !== undefined) {
      updatedUser = await userService.updateUser(
        id,
        {
          username:
            data.username !== undefined ? data.username.trim() : undefined,
          avatar: data.avatar,
        },
        selectFields
      );
    }

    return updatedUser;
  }
}

export const userStatsService = new UserStatsService();
