import { prisma } from "../database";
import bcrypt from "bcryptjs";
import type {
  User,
  CharacterClass,
  Role,
  Prisma,
} from "@/prisma/generated/prisma/client";
import { ValidationError, NotFoundError, ConflictError } from "../errors";

// Constantes de validation
const VALID_CHARACTER_CLASSES = [
  "WARRIOR",
  "MAGE",
  "ROGUE",
  "RANGER",
  "PALADIN",
  "ENGINEER",
  "MERCHANT",
  "SCHOLAR",
  "BERSERKER",
  "NECROMANCER",
] as const;

const USERNAME_MIN_LENGTH = 3;
const USERNAME_MAX_LENGTH = 20;
const BIO_MAX_LENGTH = 500;
const PASSWORD_MIN_LENGTH = 6;

export interface CreateUserInput {
  email: string;
  username: string;
  password: string;
  bio?: string | null;
  characterClass?: CharacterClass | null;
  avatar?: string | null;
}

export interface UpdateUserInput {
  username?: string;
  avatar?: string | null;
  bio?: string | null;
  characterClass?: CharacterClass | null;
}

export interface UserSelect {
  id?: boolean;
  email?: boolean;
  username?: boolean;
  avatar?: boolean;
  bio?: boolean;
  characterClass?: boolean;
  hp?: boolean;
  maxHp?: boolean;
  xp?: boolean;
  maxXp?: boolean;
  level?: boolean;
  score?: boolean;
  role?: boolean;
  createdAt?: boolean;
}

/**
 * Service de gestion des utilisateurs
 */
export class UserService {
  /**
   * Récupère un utilisateur par son ID
   */
  async getUserById(
    id: string,
    select?: Prisma.UserSelect
  ): Promise<User | null> {
    return prisma.user.findUnique({
      where: { id },
      select,
    });
  }

  /**
   * Récupère un utilisateur par son email
   */
  async getUserByEmail(email: string): Promise<User | null> {
    return prisma.user.findUnique({
      where: { email },
    });
  }

  /**
   * Récupère un utilisateur par son username
   */
  async getUserByUsername(username: string): Promise<User | null> {
    return prisma.user.findUnique({
      where: { username },
    });
  }

  /**
   * Vérifie si un username est disponible
   */
  async checkUsernameAvailability(
    username: string,
    excludeUserId?: string
  ): Promise<boolean> {
    const existingUser = await prisma.user.findFirst({
      where: {
        username: username.trim(),
        ...(excludeUserId && { NOT: { id: excludeUserId } }),
      },
    });
    return !existingUser;
  }

  /**
   * Vérifie si un email ou username existe déjà
   */
  async checkEmailOrUsernameExists(
    email: string,
    username: string
  ): Promise<boolean> {
    const existingUser = await prisma.user.findFirst({
      where: {
        OR: [{ email }, { username }],
      },
    });
    return !!existingUser;
  }

  /**
   * Crée un nouvel utilisateur
   */
  async createUser(data: CreateUserInput): Promise<User> {
    // Hasher le mot de passe
    const hashedPassword = await bcrypt.hash(data.password, 10);

    return prisma.user.create({
      data: {
        email: data.email,
        username: data.username,
        password: hashedPassword,
        bio: data.bio || null,
        characterClass: data.characterClass || null,
        avatar: data.avatar || null,
      },
    });
  }

  /**
   * Met à jour un utilisateur
   */
  async updateUser(
    id: string,
    data: UpdateUserInput,
    select?: Prisma.UserSelect
  ): Promise<User> {
    const updateData: Prisma.UserUpdateInput = {};

    if (data.username !== undefined) {
      updateData.username = data.username.trim();
    }
    if (data.avatar !== undefined) {
      updateData.avatar = data.avatar || null;
    }
    if (data.bio !== undefined) {
      updateData.bio = data.bio === null ? null : data.bio.trim() || null;
    }
    if (data.characterClass !== undefined) {
      updateData.characterClass = data.characterClass || null;
    }

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

  /**
   * Met à jour le mot de passe d'un utilisateur
   */
  async updateUserPassword(
    id: string,
    currentPassword: string,
    newPassword: string
  ): Promise<void> {
    // Récupérer l'utilisateur avec le mot de passe
    const user = await prisma.user.findUnique({
      where: { id },
      select: { password: true },
    });

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

    // Vérifier l'ancien mot de passe
    const isPasswordValid = await bcrypt.compare(
      currentPassword,
      user.password
    );

    if (!isPasswordValid) {
      throw new Error("Mot de passe actuel incorrect");
    }

    // Hasher le nouveau mot de passe
    const hashedPassword = await bcrypt.hash(newPassword, 10);

    // Mettre à jour le mot de passe
    await prisma.user.update({
      where: { id },
      data: { password: hashedPassword },
    });
  }

  /**
   * Supprime un utilisateur
   */
  async deleteUser(id: string): Promise<void> {
    await prisma.user.delete({
      where: { id },
    });
  }

  /**
   * Récupère tous les utilisateurs (pour admin)
   */
  async getAllUsers(options?: {
    orderBy?: Prisma.UserOrderByWithRelationInput;
    select?: Prisma.UserSelect;
  }): Promise<User[]> {
    return prisma.user.findMany({
      orderBy: options?.orderBy || { score: "desc" },
      select: options?.select,
    });
  }

  /**
   * Vérifie les credentials pour l'authentification
   */
  async verifyCredentials(
    email: string,
    password: string
  ): Promise<User | null> {
    const user = await prisma.user.findUnique({
      where: { email },
    });

    if (!user) {
      return null;
    }

    const isPasswordValid = await bcrypt.compare(password, user.password);

    if (!isPasswordValid) {
      return null;
    }

    return user;
  }

  /**
   * Vérifie si un compte a été créé récemment (dans les X minutes)
   */
  async isAccountRecentlyCreated(
    userId: string,
    minutesAgo: number = 10
  ): Promise<boolean> {
    const user = await prisma.user.findUnique({
      where: { id: userId },
      select: { createdAt: true },
    });

    if (!user) {
      return false;
    }

    const timeLimit = new Date(Date.now() - minutesAgo * 60 * 1000);
    return user.createdAt >= timeLimit;
  }

  /**
   * Valide et crée un utilisateur avec toutes les règles métier
   */
  async validateAndCreateUser(data: {
    email: string;
    username: string;
    password: string;
    bio?: string | null;
    characterClass?: string | null;
    avatar?: string | null;
  }): Promise<User> {
    // Validation des champs requis
    if (!data.email || !data.username || !data.password) {
      throw new ValidationError(
        "Email, nom d'utilisateur et mot de passe sont requis"
      );
    }

    // Validation du mot de passe
    if (data.password.length < PASSWORD_MIN_LENGTH) {
      throw new ValidationError(
        `Le mot de passe doit contenir au moins ${PASSWORD_MIN_LENGTH} caractères`,
        "password"
      );
    }

    // Validation du characterClass
    if (
      data.characterClass &&
      !VALID_CHARACTER_CLASSES.includes(data.characterClass as CharacterClass)
    ) {
      throw new ValidationError(
        "Classe de personnage invalide",
        "characterClass"
      );
    }

    // Vérifier si l'email ou username existe déjà
    const exists = await this.checkEmailOrUsernameExists(
      data.email,
      data.username
    );
    if (exists) {
      throw new ConflictError(
        "Cet email ou nom d'utilisateur est déjà utilisé"
      );
    }

    // Créer l'utilisateur
    return this.createUser({
      email: data.email,
      username: data.username,
      password: data.password,
      bio: data.bio || null,
      characterClass: (data.characterClass as CharacterClass) || null,
      avatar: data.avatar || null,
    });
  }

  /**
   * Valide et met à jour le profil utilisateur avec toutes les règles métier
   */
  async validateAndUpdateUserProfile(
    userId: string,
    data: {
      username?: string;
      avatar?: string | null;
      bio?: string | null;
      characterClass?: string | null;
    },
    select?: Prisma.UserSelect
  ): Promise<User> {
    // Validation username
    if (data.username !== undefined) {
      if (
        typeof data.username !== "string" ||
        data.username.trim().length === 0
      ) {
        throw new ValidationError(
          "Le nom d'utilisateur ne peut pas être vide",
          "username"
        );
      }

      if (
        data.username.length < USERNAME_MIN_LENGTH ||
        data.username.length > USERNAME_MAX_LENGTH
      ) {
        throw new ValidationError(
          `Le nom d'utilisateur doit contenir entre ${USERNAME_MIN_LENGTH} et ${USERNAME_MAX_LENGTH} caractères`,
          "username"
        );
      }

      // Vérifier si le username est déjà pris
      const isAvailable = await this.checkUsernameAvailability(
        data.username.trim(),
        userId
      );
      if (!isAvailable) {
        throw new ConflictError("Ce nom d'utilisateur est déjà pris");
      }
    }

    // Validation bio
    if (data.bio !== undefined && data.bio !== null) {
      if (typeof data.bio !== "string") {
        throw new ValidationError(
          "La bio doit être une chaîne de caractères",
          "bio"
        );
      }
      if (data.bio.length > BIO_MAX_LENGTH) {
        throw new ValidationError(
          `La bio ne peut pas dépasser ${BIO_MAX_LENGTH} caractères`,
          "bio"
        );
      }
    }

    // Validation characterClass
    if (
      data.characterClass !== undefined &&
      data.characterClass !== null &&
      !VALID_CHARACTER_CLASSES.includes(data.characterClass as CharacterClass)
    ) {
      throw new ValidationError(
        "Classe de personnage invalide",
        "characterClass"
      );
    }

    // Mettre à jour l'utilisateur
    return this.updateUser(
      userId,
      {
        username:
          data.username !== undefined ? data.username.trim() : undefined,
        avatar: data.avatar,
        bio: data.bio,
        characterClass: data.characterClass as CharacterClass | null,
      },
      select
    );
  }

  /**
   * Valide et finalise l'inscription d'un utilisateur (avec vérification du temps)
   */
  async validateAndCompleteRegistration(
    userId: string,
    data: {
      username?: string;
      avatar?: string | null;
      bio?: string | null;
      characterClass?: string | null;
    }
  ): Promise<User> {
    // Vérifier que l'utilisateur existe
    const user = await this.getUserById(userId);
    if (!user) {
      throw new NotFoundError("Utilisateur");
    }

    // Vérifier que le compte a été créé récemment (dans les 10 dernières minutes)
    const isRecent = await this.isAccountRecentlyCreated(userId, 10);
    if (!isRecent) {
      throw new ValidationError("Temps écoulé pour finaliser l'inscription");
    }

    // Valider et mettre à jour
    return this.validateAndUpdateUserProfile(userId, data);
  }

  /**
   * Valide et met à jour le mot de passe avec toutes les règles métier
   */
  async validateAndUpdatePassword(
    userId: string,
    currentPassword: string,
    newPassword: string,
    confirmPassword: string
  ): Promise<void> {
    // Validation des champs requis
    if (!currentPassword || !newPassword || !confirmPassword) {
      throw new ValidationError("Tous les champs sont requis");
    }

    // Validation du nouveau mot de passe
    if (newPassword.length < PASSWORD_MIN_LENGTH) {
      throw new ValidationError(
        `Le nouveau mot de passe doit contenir au moins ${PASSWORD_MIN_LENGTH} caractères`,
        "newPassword"
      );
    }

    // Vérifier que les mots de passe correspondent
    if (newPassword !== confirmPassword) {
      throw new ValidationError(
        "Les mots de passe ne correspondent pas",
        "confirmPassword"
      );
    }

    // Mettre à jour le mot de passe (la méthode updateUserPassword gère déjà la vérification de l'ancien mot de passe)
    await this.updateUserPassword(userId, currentPassword, newPassword);
  }

  /**
   * Valide et supprime un utilisateur avec vérification des règles métier
   */
  async validateAndDeleteUser(
    userId: string,
    currentUserId: string
  ): Promise<void> {
    // Vérifier que l'utilisateur existe
    const user = await this.getUserById(userId);
    if (!user) {
      throw new NotFoundError("Utilisateur");
    }

    // Empêcher la suppression de soi-même
    if (user.id === currentUserId) {
      throw new ValidationError(
        "Vous ne pouvez pas supprimer votre propre compte"
      );
    }

    // Supprimer l'utilisateur
    await this.deleteUser(userId);
  }
}

export const userService = new UserService();
