import { AppError } from '../errors/app-error';
import {
  CreerEmpruntRequest,
  RestituerEmpruntRequest,
  ChecklistElementRequest,
} from '../dtos/emprunt.dto';
import {
  findEnCoursParUtilisateur,
  creerEmpruntComplet,
  findByIdAvecChecklists,
  restituerEmpruntComplet,
  EmpruntAvecMateriel,
  RestituerEmpruntData,
} from '../repositories/emprunt.repository';
import { findById as findMaterielById } from '../repositories/materiel.repository';
import { findById as findPosteById } from '../repositories/poste-emprunt.repository';
import { findResponsablesParCampus } from '../repositories/utilisateur.repository';
import { StatutEmprunt } from '../models/enums';

const DUREE_EMPRUNT_JOURS = 14;
const STATUTS_RESTITUABLES: readonly string[] = ['EN_COURS', 'EN_RETARD'];

/* RG15 : un étudiant ne consulte que ses propres emprunts non restitués. */
export function listerMesEmpruntsEnCours(utilisateurId: number): Promise<EmpruntAvecMateriel[]> {
  return findEnCoursParUtilisateur(utilisateurId);
}

/* RG07/RG10/RG11/RG12 : création d'un emprunt avec sa checklist de départ.
   Les contrôles d'éligibilité (campus, disponibilité) sont faits ici ; l'écriture
   atomique est déléguée au repository. */
export async function creerEmprunt(
  utilisateurId: number,
  campusId: number,
  request: CreerEmpruntRequest,
): Promise<EmpruntAvecMateriel> {
  const materiel = await findMaterielById(request.materielId);
  if (!materiel) {
    throw new AppError(404, 'Materiel introuvable');
  }
  if (materiel.campusId !== campusId) {
    throw new AppError(403, 'Materiel rattache a un autre campus');
  }
  if (materiel.statut !== 'DISPONIBLE') {
    throw new AppError(409, 'Materiel non disponible');
  }

  const poste = await findPosteById(request.posteEmpruntId);
  if (!poste) {
    throw new AppError(400, 'Poste introuvable');
  }
  if (poste.sallePret.campusId !== campusId) {
    throw new AppError(403, 'Poste rattache a un autre campus');
  }

  const dateRetourPrevue = new Date();
  dateRetourPrevue.setDate(dateRetourPrevue.getDate() + DUREE_EMPRUNT_JOURS);

  return creerEmpruntComplet({
    utilisateurId,
    materielId: request.materielId,
    campusId,
    sallePretId: poste.sallePretId,
    posteEmpruntId: request.posteEmpruntId,
    dateRetourPrevue,
    modeIdentification: request.modeIdentification,
    commentaireDepart: request.commentaireDepart,
    elements: request.elements,
  });
}

interface ElementDepart {
  accessoireId: number | null;
  nomElement: string;
  etat: string;
}

interface ResultatComparaison {
  conforme: boolean;
  auMoinsUnDeteriore: boolean;
  details: string[];
}

function trouverRetour(
  depart: ElementDepart,
  retour: ChecklistElementRequest[],
): ChecklistElementRequest | undefined {
  if (depart.accessoireId !== null) {
    const parId = retour.find((element) => element.accessoireId === depart.accessoireId);
    if (parId) {
      return parId;
    }
  }
  return retour.find((element) => element.nomElement === depart.nomElement);
}

/* RG17 : compare la checklist de départ à celle de retour. Un élément présent au
   départ mais absent ou détérioré au retour rend la restitution non conforme (RG20). */
function comparerChecklists(
  depart: ElementDepart[],
  retour: ChecklistElementRequest[],
): ResultatComparaison {
  const details: string[] = [];
  let auMoinsUnDeteriore = false;

  for (const elementDepart of depart) {
    if (elementDepart.etat !== 'PRESENT') {
      continue;
    }

    const elementRetour = trouverRetour(elementDepart, retour);
    if (!elementRetour || elementRetour.etat === 'ABSENT') {
      details.push(`${elementDepart.nomElement} manquant au retour`);
    } else if (elementRetour.etat === 'DETERIORE') {
      details.push(`${elementDepart.nomElement} deteriore au retour`);
      auMoinsUnDeteriore = true;
    }
  }

  return { conforme: details.length === 0, auMoinsUnDeteriore, details };
}

/* RG14-RG20 : restitution d'un emprunt avec comparaison des checklists, clôture
   ou passage en non conforme, et création automatique d'anomalie + notifications. */
export async function restituerEmprunt(
  utilisateurId: number,
  empruntId: number,
  request: RestituerEmpruntRequest,
): Promise<EmpruntAvecMateriel> {
  const emprunt = await findByIdAvecChecklists(empruntId);
  if (!emprunt) {
    throw new AppError(404, 'Emprunt introuvable');
  }
  if (emprunt.utilisateurId !== utilisateurId) {
    throw new AppError(403, 'Cet emprunt ne vous appartient pas');
  }
  if (!STATUTS_RESTITUABLES.includes(emprunt.statut)) {
    throw new AppError(409, 'Emprunt non restituable');
  }

  const checklistDepart = emprunt.checklists.find((checklist) => checklist.type === 'DEPART');
  if (!checklistDepart) {
    throw new AppError(409, 'Checklist de depart introuvable');
  }

  const comparaison = comparerChecklists(
    checklistDepart.elements.map((element) => ({
      accessoireId: element.accessoireId,
      nomElement: element.nomElement,
      etat: element.etat,
    })),
    request.elements,
  );

  const statutEmprunt: StatutEmprunt = comparaison.conforme ? 'CLOTURE' : 'RETOUR_NON_CONFORME';

  let statutMateriel = 'DISPONIBLE';
  if (!comparaison.conforme) {
    statutMateriel = comparaison.auMoinsUnDeteriore ? 'DETERIORE' : 'NON_CONFORME';
  }

  const donnees: RestituerEmpruntData = {
    empruntId,
    utilisateurId,
    materielId: emprunt.materielId,
    statutEmprunt,
    statutMateriel,
    modeIdentification: request.modeIdentification,
    commentaireRetour: request.commentaireRetour,
    elements: request.elements,
  };

  if (!comparaison.conforme) {
    const responsables = await findResponsablesParCampus(emprunt.campusId);
    donnees.anomalie = {
      type: 'RETOUR_NON_CONFORME',
      description: `Retour non conforme : ${comparaison.details.join(', ')}.`,
      responsablesIds: responsables.map((responsable) => responsable.id),
    };
  }

  return restituerEmpruntComplet(donnees);
}
