import { prisma } from "../database";
import type {
  Event,
  EventType,
  Prisma,
} from "@/prisma/generated/prisma/client";
import { ValidationError, NotFoundError } from "../errors";
import { calculateEventStatus } from "@/lib/eventStatus";

export interface CreateEventInput {
  date: Date;
  name: string;
  description: string;
  type: EventType;
  room?: string | null;
  time?: string | null;
  maxPlaces?: number | null;
}

export interface UpdateEventInput {
  date?: Date;
  name?: string;
  description?: string;
  type?: EventType;
  room?: string | null;
  time?: string | null;
  maxPlaces?: number | null;
}

export interface EventWithRegistrationsCount extends Event {
  registrationsCount: number;
}

/**
 * Service de gestion des événements
 */
export class EventService {
  /**
   * Récupère tous les événements
   */
  async getAllEvents(options?: {
    orderBy?: Prisma.EventOrderByWithRelationInput;
    take?: number;
  }): Promise<Event[]> {
    return prisma.event.findMany({
      orderBy: options?.orderBy || { date: "asc" },
      ...(options?.take && { take: options.take }),
    });
  }

  /**
   * Récupère un événement par son ID
   */
  async getEventById(id: string): Promise<Event | null> {
    return prisma.event.findUnique({
      where: { id },
    });
  }

  /**
   * Récupère les événements à venir
   */
  async getUpcomingEvents(limit: number = 3): Promise<Event[]> {
    const now = new Date();
    return prisma.event.findMany({
      where: {
        date: {
          gte: now,
        },
      },
      orderBy: {
        date: "asc",
      },
      take: limit,
    });
  }

  /**
   * Crée un nouvel événement
   */
  async createEvent(data: CreateEventInput): Promise<Event> {
    return prisma.event.create({
      data: {
        date: data.date,
        name: data.name,
        description: data.description,
        type: data.type,
        room: data.room || null,
        time: data.time || null,
        maxPlaces: data.maxPlaces ? parseInt(String(data.maxPlaces)) : null,
      },
    });
  }

  /**
   * Met à jour un événement
   */
  async updateEvent(id: string, data: UpdateEventInput): Promise<Event> {
    const updateData: Prisma.EventUpdateInput = {};

    if (data.date !== undefined) {
      updateData.date = data.date;
    }
    if (data.name !== undefined) {
      updateData.name = data.name;
    }
    if (data.description !== undefined) {
      updateData.description = data.description;
    }
    if (data.type !== undefined) {
      updateData.type = data.type;
    }
    if (data.room !== undefined) {
      updateData.room = data.room || null;
    }
    if (data.time !== undefined) {
      updateData.time = data.time || null;
    }
    if (data.maxPlaces !== undefined) {
      updateData.maxPlaces = data.maxPlaces
        ? parseInt(String(data.maxPlaces))
        : null;
    }

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

  /**
   * Supprime un événement
   */
  async deleteEvent(id: string): Promise<void> {
    await prisma.event.delete({
      where: { id },
    });
  }

  /**
   * Récupère les événements avec le nombre d'inscriptions (pour admin)
   */
  async getEventsWithRegistrationsCount(): Promise<
    EventWithRegistrationsCount[]
  > {
    const events = await prisma.event.findMany({
      orderBy: {
        date: "desc",
      },
      include: {
        _count: {
          select: {
            registrations: true,
          },
        },
      },
    });

    return events.map((event) => ({
      ...event,
      registrationsCount: event._count.registrations,
    }));
  }

  /**
   * Récupère les événements avec leur statut calculé (pour admin)
   */
  async getEventsWithStatus(): Promise<
    Array<
      EventWithRegistrationsCount & { status: "UPCOMING" | "LIVE" | "PAST" }
    >
  > {
    const events = await this.getEventsWithRegistrationsCount();
    return events.map((event) => ({
      ...event,
      status: calculateEventStatus(event.date),
    }));
  }

  /**
   * Valide et crée un événement avec toutes les règles métier
   */
  async validateAndCreateEvent(data: {
    date: string | Date;
    name: string;
    description: string;
    type: string;
    room?: string | null;
    time?: string | null;
    maxPlaces?: string | number | null;
  }): Promise<Event> {
    // Validation des champs requis
    if (!data.date || !data.name || !data.description || !data.type) {
      throw new ValidationError("Tous les champs sont requis");
    }

    // Convertir et valider la date
    const eventDate =
      typeof data.date === "string" ? new Date(data.date) : data.date;
    if (isNaN(eventDate.getTime())) {
      throw new ValidationError("Format de date invalide", "date");
    }

    // Valider le type d'événement
    if (!Object.values(EventType).includes(data.type as EventType)) {
      throw new ValidationError("Type d'événement invalide", "type");
    }

    // Créer l'événement
    return this.createEvent({
      date: eventDate,
      name: data.name,
      description: data.description,
      type: data.type as EventType,
      room: data.room || null,
      time: data.time || null,
      maxPlaces: data.maxPlaces ? parseInt(String(data.maxPlaces)) : null,
    });
  }

  /**
   * Valide et met à jour un événement avec toutes les règles métier
   */
  async validateAndUpdateEvent(
    id: string,
    data: {
      date?: string | Date;
      name?: string;
      description?: string;
      type?: string;
      room?: string | null;
      time?: string | null;
      maxPlaces?: string | number | null;
    }
  ): Promise<Event> {
    // Vérifier que l'événement existe
    const existingEvent = await this.getEventById(id);
    if (!existingEvent) {
      throw new NotFoundError("Événement");
    }

    const updateData: UpdateEventInput = {};

    // Valider et convertir la date si fournie
    if (data.date !== undefined) {
      const eventDate =
        typeof data.date === "string" ? new Date(data.date) : data.date;
      if (isNaN(eventDate.getTime())) {
        throw new ValidationError("Format de date invalide", "date");
      }
      updateData.date = eventDate;
    }

    // Valider le type si fourni
    if (data.type !== undefined) {
      if (!Object.values(EventType).includes(data.type as EventType)) {
        throw new ValidationError("Type d'événement invalide", "type");
      }
      updateData.type = data.type as EventType;
    }

    // Autres champs
    if (data.name !== undefined) updateData.name = data.name;
    if (data.description !== undefined)
      updateData.description = data.description;
    if (data.room !== undefined) updateData.room = data.room || null;
    if (data.time !== undefined) updateData.time = data.time || null;
    if (data.maxPlaces !== undefined) {
      updateData.maxPlaces = data.maxPlaces
        ? parseInt(String(data.maxPlaces))
        : null;
    }

    return this.updateEvent(id, updateData);
  }
}

export const eventService = new EventService();
