import { Prisma } from '@prisma/client';
import { prisma } from '../db/prisma';
import { StatutEmprunt } from '../models/enums';
import { AppError } from '../errors/app-error';

export type EmpruntAvecMateriel = Prisma.EmpruntGetPayload<{
  include: { materiel: { include: { categorie: true } } };
}>;

export type EmpruntAvecChecklists = Prisma.EmpruntGetPayload<{
  include: { checklists: { include: { elements: true } } };
}>;

const STATUTS_EN_COURS: StatutEmprunt[] = ['EN_COURS', 'EN_RETARD'];

export function findEnCoursParUtilisateur(utilisateurId: number): Promise<EmpruntAvecMateriel[]> {
  return prisma.emprunt.findMany({
    where: {
      utilisateurId,
      statut: { in: STATUTS_EN_COURS },
    },
    include: { materiel: { include: { categorie: true } } },
    orderBy: { dateRetourPrevue: 'asc' },
  });
}

export function findByIdAvecChecklists(id: number): Promise<EmpruntAvecChecklists | null> {
  return prisma.emprunt.findUnique({
    where: { id },
    include: { checklists: { include: { elements: true } } },
  });
}

interface ChecklistElementData {
  accessoireId?: number;
  nomElement: string;
  etat: string;
  quantiteConstatee: number;
  commentaire?: string;
}

export interface CreerEmpruntData {
  utilisateurId: number;
  materielId: number;
  campusId: number;
  sallePretId: number;
  posteEmpruntId: number;
  dateRetourPrevue: Date;
  modeIdentification: string;
  commentaireDepart?: string;
  elements: ChecklistElementData[];
}

/* Création atomique : réservation du matériel (DISPONIBLE -> EMPRUNTE), emprunt,
   checklist de départ et ses éléments. Si une étape échoue, tout est annulé. */
export function creerEmpruntComplet(data: CreerEmpruntData): Promise<EmpruntAvecMateriel> {
  return prisma.$transaction(async (tx) => {
    // Réservation atomique : empêche un double emprunt du même matériel (RG07).
    const reservation = await tx.materiel.updateMany({
      where: { id: data.materielId, statut: 'DISPONIBLE' },
      data: { statut: 'EMPRUNTE' },
    });
    if (reservation.count === 0) {
      throw new AppError(409, 'Materiel non disponible');
    }

    const emprunt = await tx.emprunt.create({
      data: {
        utilisateurId: data.utilisateurId,
        materielId: data.materielId,
        campusId: data.campusId,
        sallePretId: data.sallePretId,
        posteEmpruntId: data.posteEmpruntId,
        dateEmprunt: new Date(),
        dateRetourPrevue: data.dateRetourPrevue,
        statut: 'EN_COURS',
        modeIdentificationEmprunt: data.modeIdentification,
        commentaireDepart: data.commentaireDepart ?? null,
      },
    });

    await tx.checklist.create({
      data: {
        empruntId: emprunt.id,
        utilisateurId: data.utilisateurId,
        type: 'DEPART',
        dateVerification: new Date(),
        elements: {
          create: data.elements.map((element) => ({
            accessoireId: element.accessoireId ?? null,
            nomElement: element.nomElement,
            etat: element.etat,
            quantiteConstatee: element.quantiteConstatee,
            commentaire: element.commentaire ?? null,
          })),
        },
      },
    });

    return tx.emprunt.findUniqueOrThrow({
      where: { id: emprunt.id },
      include: { materiel: { include: { categorie: true } } },
    });
  });
}

export interface RestituerEmpruntData {
  empruntId: number;
  utilisateurId: number;
  materielId: number;
  statutEmprunt: string;
  statutMateriel: string;
  modeIdentification: string;
  commentaireRetour?: string;
  elements: ChecklistElementData[];
  anomalie?: {
    type: string;
    description: string;
    responsablesIds: number[];
  };
}

/* Restitution atomique : checklist de retour, mise à jour de l'emprunt et du
   matériel, et — si non conforme — création de l'anomalie et des notifications. */
export function restituerEmpruntComplet(data: RestituerEmpruntData): Promise<EmpruntAvecMateriel> {
  return prisma.$transaction(async (tx) => {
    const maintenant = new Date();

    await tx.checklist.create({
      data: {
        empruntId: data.empruntId,
        utilisateurId: data.utilisateurId,
        type: 'RETOUR',
        dateVerification: maintenant,
        elements: {
          create: data.elements.map((element) => ({
            accessoireId: element.accessoireId ?? null,
            nomElement: element.nomElement,
            etat: element.etat,
            quantiteConstatee: element.quantiteConstatee,
            commentaire: element.commentaire ?? null,
          })),
        },
      },
    });

    await tx.emprunt.update({
      where: { id: data.empruntId },
      data: {
        statut: data.statutEmprunt,
        dateRetourReelle: maintenant,
        modeIdentificationRetour: data.modeIdentification,
        commentaireRetour: data.commentaireRetour ?? null,
      },
    });

    await tx.materiel.update({
      where: { id: data.materielId },
      data: { statut: data.statutMateriel },
    });

    if (data.anomalie) {
      const infoAnomalie = data.anomalie;
      const anomalie = await tx.anomalie.create({
        data: {
          empruntId: data.empruntId,
          materielId: data.materielId,
          utilisateurId: data.utilisateurId,
          type: infoAnomalie.type,
          description: infoAnomalie.description,
          statut: 'DETECTEE',
          detecteeAutomatiquement: true,
          dateDetection: maintenant,
        },
      });

      if (infoAnomalie.responsablesIds.length > 0) {
        await tx.notification.createMany({
          data: infoAnomalie.responsablesIds.map((responsableId) => ({
            utilisateurId: responsableId,
            anomalieId: anomalie.id,
            titre: 'Nouvelle anomalie detectee',
            message: infoAnomalie.description,
            type: 'ANOMALIE',
          })),
        });
      }
    }

    return tx.emprunt.findUniqueOrThrow({
      where: { id: data.empruntId },
      include: { materiel: { include: { categorie: true } } },
    });
  });
}
