import { prisma } from "../database";
import type {
  Challenge,
  ChallengeStatus,
  Prisma,
} from "@/prisma/generated/prisma/client";
import { ValidationError, NotFoundError } from "../errors";

export interface CreateChallengeInput {
  challengerId: string;
  challengedId: string;
  title: string;
  description: string;
  pointsReward?: number;
}

export interface UpdateChallengeInput {
  status?: ChallengeStatus;
  adminId?: string;
  adminComment?: string;
  winnerId?: string;
  title?: string;
  description?: string;
  pointsReward?: number;
}

export interface ChallengeWithUsers extends Challenge {
  challenger: {
    id: string;
    username: string;
    avatar: string | null;
  };
  challenged: {
    id: string;
    username: string;
    avatar: string | null;
  };
  admin?: {
    id: string;
    username: string;
  } | null;
  winner?: {
    id: string;
    username: string;
  } | null;
}

/**
 * Service de gestion des défis entre joueurs
 */
export class ChallengeService {
  /**
   * Crée un nouveau défi
   */
  async createChallenge(data: CreateChallengeInput): Promise<Challenge> {
    // Vérifier que les deux joueurs existent
    const [challenger, challenged] = await Promise.all([
      prisma.user.findUnique({ where: { id: data.challengerId } }),
      prisma.user.findUnique({ where: { id: data.challengedId } }),
    ]);

    if (!challenger) {
      throw new NotFoundError("Joueur qui lance le défi");
    }

    if (!challenged) {
      throw new NotFoundError("Joueur qui reçoit le défi");
    }

    // Vérifier qu'on ne se défie pas soi-même
    if (data.challengerId === data.challengedId) {
      throw new ValidationError("Vous ne pouvez pas vous défier vous-même");
    }

    return prisma.challenge.create({
      data: {
        challengerId: data.challengerId,
        challengedId: data.challengedId,
        title: data.title,
        description: data.description,
        pointsReward: data.pointsReward || 100,
        status: "PENDING",
      },
    });
  }

  /**
   * Accepte un défi
   */
  async acceptChallenge(
    challengeId: string,
    userId: string
  ): Promise<Challenge> {
    const challenge = await prisma.challenge.findUnique({
      where: { id: challengeId },
    });

    if (!challenge) {
      throw new NotFoundError("Défi");
    }

    // Vérifier que l'utilisateur est bien celui qui reçoit le défi
    if (challenge.challengedId !== userId) {
      throw new ValidationError("Vous n'êtes pas autorisé à accepter ce défi");
    }

    // Vérifier que le défi est en attente
    if (challenge.status !== "PENDING") {
      throw new ValidationError(
        "Ce défi ne peut plus être accepté (statut: " + challenge.status + ")"
      );
    }

    return prisma.challenge.update({
      where: { id: challengeId },
      data: {
        status: "ACCEPTED",
        acceptedAt: new Date(),
      },
    });
  }

  /**
   * Accepte un défi en tant qu'admin (bypass les vérifications utilisateur)
   */
  async adminAcceptChallenge(challengeId: string): Promise<Challenge> {
    const challenge = await prisma.challenge.findUnique({
      where: { id: challengeId },
    });

    if (!challenge) {
      throw new NotFoundError("Défi");
    }

    // Vérifier que le défi est en attente
    if (challenge.status !== "PENDING") {
      throw new ValidationError(
        "Ce défi ne peut plus être accepté (statut: " + challenge.status + ")"
      );
    }

    return prisma.challenge.update({
      where: { id: challengeId },
      data: {
        status: "ACCEPTED",
        acceptedAt: new Date(),
      },
    });
  }

  /**
   * Annule un défi (par le challenger ou le challenged)
   */
  async cancelChallenge(
    challengeId: string,
    userId: string
  ): Promise<Challenge> {
    const challenge = await prisma.challenge.findUnique({
      where: { id: challengeId },
    });

    if (!challenge) {
      throw new NotFoundError("Défi");
    }

    // Vérifier que l'utilisateur est bien impliqué dans le défi
    if (
      challenge.challengerId !== userId &&
      challenge.challengedId !== userId
    ) {
      throw new ValidationError("Vous n'êtes pas autorisé à annuler ce défi");
    }

    // Vérifier que le défi peut être annulé
    if (challenge.status === "COMPLETED") {
      throw new ValidationError("Un défi complété ne peut pas être annulé");
    }

    return prisma.challenge.update({
      where: { id: challengeId },
      data: {
        status: "CANCELLED",
      },
    });
  }

  /**
   * Valide un défi (admin seulement)
   */
  async validateChallenge(
    challengeId: string,
    adminId: string,
    winnerId: string,
    adminComment?: string
  ): Promise<Challenge> {
    // Récupérer uniquement les champs nécessaires pour la validation
    const challenge = await prisma.challenge.findUnique({
      where: { id: challengeId },
      select: {
        id: true,
        status: true,
        challengerId: true,
        challengedId: true,
        pointsReward: true,
      },
    });

    if (!challenge) {
      throw new NotFoundError("Défi");
    }

    // Vérifier que le défi est accepté
    if (challenge.status !== "ACCEPTED") {
      throw new ValidationError(
        "Seuls les défis acceptés peuvent être validés"
      );
    }

    // Vérifier que le winner est bien l'un des deux joueurs
    if (
      winnerId !== challenge.challengerId &&
      winnerId !== challenge.challengedId
    ) {
      throw new ValidationError(
        "Le gagnant doit être l'un des deux joueurs du défi"
      );
    }

    // Paralléliser la mise à jour du défi et l'attribution des points
    const [updatedChallenge] = await Promise.all([
      prisma.challenge.update({
        where: { id: challengeId },
        data: {
          status: "COMPLETED",
          adminId,
          adminComment: adminComment || null,
          winnerId,
          completedAt: new Date(),
        },
        include: {
          challenger: {
            select: {
              id: true,
              username: true,
              avatar: true,
            },
          },
          challenged: {
            select: {
              id: true,
              username: true,
              avatar: true,
            },
          },
          winner: {
            select: {
              id: true,
              username: true,
            },
          },
        },
      }),
      prisma.user.update({
        where: { id: winnerId },
        data: {
          score: {
            increment: challenge.pointsReward,
          },
        },
      }),
    ]);

    return updatedChallenge;
  }

  /**
   * Rejette un défi (admin seulement)
   */
  async rejectChallenge(
    challengeId: string,
    adminId: string,
    adminComment?: string
  ): Promise<Challenge> {
    const challenge = await prisma.challenge.findUnique({
      where: { id: challengeId },
    });

    if (!challenge) {
      throw new NotFoundError("Défi");
    }

    // Vérifier que le défi est accepté
    if (challenge.status !== "ACCEPTED") {
      throw new ValidationError(
        "Seuls les défis acceptés peuvent être rejetés"
      );
    }

    return prisma.challenge.update({
      where: { id: challengeId },
      data: {
        status: "REJECTED",
        adminId,
        adminComment: adminComment || null,
      },
    });
  }

  /**
   * Met à jour un défi (admin seulement)
   */
  async updateChallenge(
    challengeId: string,
    data: UpdateChallengeInput
  ): Promise<Challenge> {
    const updateData: Prisma.ChallengeUpdateInput = {};

    if (data.title !== undefined) {
      updateData.title = data.title;
    }
    if (data.description !== undefined) {
      updateData.description = data.description;
    }
    if (data.pointsReward !== undefined) {
      updateData.pointsReward = data.pointsReward;
    }
    if (data.status !== undefined) {
      updateData.status = data.status;
    }
    if (data.adminId !== undefined) {
      updateData.admin = data.adminId
        ? { connect: { id: data.adminId } }
        : { disconnect: true };
    }
    if (data.adminComment !== undefined) {
      updateData.adminComment = data.adminComment;
    }
    if (data.winnerId !== undefined) {
      updateData.winner = data.winnerId
        ? { connect: { id: data.winnerId } }
        : { disconnect: true };
    }

    try {
      return await prisma.challenge.update({
        where: { id: challengeId },
        data: updateData,
      });
    } catch (error: any) {
      if (error?.code === "P2025") {
        // Record not found
        throw new NotFoundError("Défi");
      }
      throw error;
    }
  }

  /**
   * Annule un défi (admin seulement)
   */
  async adminCancelChallenge(challengeId: string): Promise<Challenge> {
    // Récupérer uniquement le statut pour la validation
    const challenge = await prisma.challenge.findUnique({
      where: { id: challengeId },
      select: {
        id: true,
        status: true,
      },
    });

    if (!challenge) {
      throw new NotFoundError("Défi");
    }

    // Vérifier que le défi peut être annulé
    if (challenge.status === "COMPLETED") {
      throw new ValidationError("Un défi complété ne peut pas être annulé");
    }

    return prisma.challenge.update({
      where: { id: challengeId },
      data: {
        status: "CANCELLED",
      },
    });
  }

  /**
   * Réactive un défi annulé (admin seulement)
   * Remet le défi en PENDING s'il n'avait jamais été accepté, sinon en ACCEPTED
   */
  async reactivateChallenge(challengeId: string): Promise<Challenge> {
    // Récupérer uniquement les champs nécessaires
    const challenge = await prisma.challenge.findUnique({
      where: { id: challengeId },
      select: {
        id: true,
        status: true,
        acceptedAt: true,
      },
    });

    if (!challenge) {
      throw new NotFoundError("Défi");
    }

    // Vérifier que le défi est annulé
    if (challenge.status !== "CANCELLED") {
      throw new ValidationError(
        "Seuls les défis annulés peuvent être réactivés"
      );
    }

    // Si le défi avait été accepté avant, le remettre en ACCEPTED
    // Sinon, le remettre en PENDING
    const newStatus = challenge.acceptedAt ? "ACCEPTED" : "PENDING";

    return prisma.challenge.update({
      where: { id: challengeId },
      data: {
        status: newStatus,
      },
    });
  }

  /**
   * Supprime un défi (admin seulement)
   */
  async deleteChallenge(challengeId: string): Promise<void> {
    try {
      await prisma.challenge.delete({
        where: { id: challengeId },
      });
    } catch (error: any) {
      if (error?.code === "P2025") {
        // Record not found
        throw new NotFoundError("Défi");
      }
      throw error;
    }
  }

  /**
   * Récupère un défi par son ID avec les utilisateurs
   */
  async getChallengeById(id: string): Promise<ChallengeWithUsers | null> {
    const challenge = await prisma.challenge.findUnique({
      where: { id },
      include: {
        challenger: {
          select: {
            id: true,
            username: true,
            avatar: true,
          },
        },
        challenged: {
          select: {
            id: true,
            username: true,
            avatar: true,
          },
        },
        admin: {
          select: {
            id: true,
            username: true,
          },
        },
        winner: {
          select: {
            id: true,
            username: true,
          },
        },
      },
    });

    return challenge as ChallengeWithUsers | null;
  }

  /**
   * Récupère tous les défis d'un utilisateur
   */
  async getUserChallenges(userId: string): Promise<ChallengeWithUsers[]> {
    return prisma.challenge.findMany({
      where: {
        OR: [{ challengerId: userId }, { challengedId: userId }],
      },
      include: {
        challenger: {
          select: {
            id: true,
            username: true,
            avatar: true,
          },
        },
        challenged: {
          select: {
            id: true,
            username: true,
            avatar: true,
          },
        },
        admin: {
          select: {
            id: true,
            username: true,
          },
        },
        winner: {
          select: {
            id: true,
            username: true,
          },
        },
      },
      orderBy: {
        createdAt: "desc",
      },
    }) as Promise<ChallengeWithUsers[]>;
  }

  /**
   * Récupère les défis acceptés en attente de désignation du gagnant
   */
  async getPendingValidationChallenges(): Promise<ChallengeWithUsers[]> {
    return prisma.challenge.findMany({
      where: {
        status: "ACCEPTED",
      },
      include: {
        challenger: {
          select: {
            id: true,
            username: true,
            avatar: true,
          },
        },
        challenged: {
          select: {
            id: true,
            username: true,
            avatar: true,
          },
        },
        admin: {
          select: {
            id: true,
            username: true,
          },
        },
        winner: {
          select: {
            id: true,
            username: true,
          },
        },
      },
      orderBy: {
        acceptedAt: "asc",
      },
    }) as Promise<ChallengeWithUsers[]>;
  }

  /**
   * Récupère tous les défis (pour admin)
   */
  async getAllChallenges(options?: {
    status?: ChallengeStatus;
    take?: number;
  }): Promise<ChallengeWithUsers[]> {
    return prisma.challenge.findMany({
      where: options?.status ? { status: options.status } : undefined,
      include: {
        challenger: {
          select: {
            id: true,
            username: true,
            avatar: true,
          },
        },
        challenged: {
          select: {
            id: true,
            username: true,
            avatar: true,
          },
        },
        admin: {
          select: {
            id: true,
            username: true,
          },
        },
        winner: {
          select: {
            id: true,
            username: true,
          },
        },
      },
      orderBy: {
        createdAt: "desc",
      },
      take: options?.take,
    }) as Promise<ChallengeWithUsers[]>;
  }

  /**
   * Compte les défis actifs pour un utilisateur
   * - PENDING où l'utilisateur est challenged (en attente de sa réponse)
   * - ACCEPTED où l'utilisateur est challenger ou challenged (en cours, en attente de validation)
   */
  async getActiveChallengesCount(userId: string): Promise<number> {
    return prisma.challenge.count({
      where: {
        OR: [
          // Défis en attente de réponse de l'utilisateur
          {
            status: "PENDING",
            challengedId: userId,
          },
          // Défis acceptés en cours (en attente de désignation du gagnant)
          {
            status: "ACCEPTED",
            OR: [{ challengerId: userId }, { challengedId: userId }],
          },
        ],
      },
    });
  }
}

export const challengeService = new ChallengeService();
