package dev.lions.components;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.validation.constraints.NotNull;

import java.io.Serial;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import org.primefaces.event.FileUploadEvent;
import org.primefaces.model.file.UploadedFile;

import dev.lions.config.ApplicationConfig;
import dev.lions.exceptions.FileUploadException;
import dev.lions.services.FileStorageService;
import dev.lions.utils.FileValidator;
import dev.lions.utils.SecurityUtils;

import java.io.Serializable;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * Composant de gestion des téléchargements de fichiers.
 * Fournit une interface sécurisée et performante pour le téléchargement,
 * la validation et la gestion des fichiers dans l'application.
 *
 * @author Lions Dev Team
 * @version 2.1
 */
@Named
@ViewScoped
@Slf4j
public class FileUploadComponent implements Serializable {

  @Serial
  private static final long serialVersionUID = 1L;

  private static final int MAX_FILES = 10; // Limite de fichiers autorisés
  private static final String TEMP_DIR_PREFIX = "upload_"; // Préfixe pour répertoire temporaire

  @Inject
  ApplicationConfig appConfig;

  @Inject
  FileStorageService storageService;

  @Inject
  FileValidator fileValidator;

  @Inject
  SecurityUtils securityUtils;

  @Getter
  private final List<UploadedFileInfo> uploadedFiles = new ArrayList<>();

  @Getter
  @Setter
  private String uploadDirectory;

  @Getter
  @Setter
  private boolean multiple = false;

  @Getter
  @Setter
  private String acceptedTypes;

  @Getter
  @Setter
  private long maxFileSize;

  /**
   * Initialisation du composant.
   */
  @PostConstruct
  public void init() {
    this.maxFileSize = appConfig.getMaxFileSize();
    this.acceptedTypes = appConfig.getAllowedFileTypes();
    this.uploadDirectory = createTempUploadDirectory();

    log.info("Composant de téléchargement initialisé. Taille max: {}, Types acceptés: {}",
             maxFileSize, acceptedTypes);
  }

  /**
   * Gère l'événement de téléchargement de fichier.
   *
   * @param event L'événement PrimeFaces contenant le fichier téléchargé.
   */
  public void handleFileUpload(@NotNull FileUploadEvent event) {
    UploadedFile file = event.getFile();
    log.info("Téléchargement de fichier : {}", file.getFileName());

    try {
      validateUploadRequest(file);
      UploadedFileInfo fileInfo = processUploadedFile(file);
      uploadedFiles.add(fileInfo);

      addSuccessMessage("Fichier téléchargé avec succès : " + fileInfo.getFileName());
    } catch (FileUploadException e) {
      log.error("Erreur de validation du fichier : {}", file.getFileName(), e);
      addErrorMessage(e.getMessage());
    } catch (IOException e) {
      log.error("Erreur lors du traitement du fichier : {}", file.getFileName(), e);
      addErrorMessage("Une erreur est survenue lors du traitement du fichier.");
    }
  }

  /**
   * Valide la requête de téléchargement.
   *
   * @param file Le fichier téléchargé.
   */
  private void validateUploadRequest(UploadedFile file) {
    if (uploadedFiles.size() >= MAX_FILES) {
      throw new FileUploadException("Vous avez atteint le nombre maximum de fichiers autorisés.");
    }
    fileValidator.validateFile(file, acceptedTypes, maxFileSize);
  }

  /**
   * Traite et stocke le fichier téléchargé.
   *
   * @param file Le fichier téléchargé.
   * @return Les informations du fichier.
   * @throws IOException En cas d'erreur de stockage.
   */
  private UploadedFileInfo processUploadedFile(UploadedFile file) throws IOException {
    String secureFileName = generateSecureFileName(file.getFileName());
    Path destinationPath = storageService.storeFile(file.getInputStream(), uploadDirectory, secureFileName);

    return UploadedFileInfo.builder()
                           .id(UUID.randomUUID().toString())
                           .fileName(file.getFileName())
                           .contentType(file.getContentType())
                           .size(file.getSize())
                           .path(destinationPath)
                           .build();
  }

  /**
   * Génère un nom de fichier sécurisé.
   *
   * @param originalFileName Nom original.
   * @return Nom sécurisé.
   */
  private String generateSecureFileName(String originalFileName) {
    String extension = getFileExtension(originalFileName);
    return securityUtils.sanitizeFileName(UUID.randomUUID().toString() + "." + extension);
  }

  /**
   * Récupère l'extension d'un fichier.
   *
   * @param fileName Nom du fichier.
   * @return Extension.
   */
  private String getFileExtension(String fileName) {
    return fileName.substring(fileName.lastIndexOf('.') + 1);
  }

  /**
   * Crée un répertoire temporaire pour les téléchargements.
   *
   * @return Le chemin du répertoire temporaire.
   */
  private String createTempUploadDirectory() {
    return storageService.createTempDirectory(TEMP_DIR_PREFIX + UUID.randomUUID());
  }

  /**
   * Nettoie les ressources lors de la destruction du composant.
   */
  @PreDestroy
  public void cleanup() {
    try {
      storageService.deleteDirectory(uploadDirectory);
      log.info("Répertoire temporaire supprimé : {}", uploadDirectory);
    } catch (Exception e) {
      log.error("Erreur lors du nettoyage des ressources : {}", uploadDirectory, e);
    }
  }

  /**
   * Ajoute un message de succès dans l'interface utilisateur.
   *
   * @param message Message à afficher.
   */
  private void addSuccessMessage(String message) {
    FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
  }

  /**
   * Ajoute un message d'erreur dans l'interface utilisateur.
   *
   * @param message Message à afficher.
   */
  private void addErrorMessage(String message) {
    FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
  }

  /**
   * Classe interne représentant un fichier téléchargé.
   */
  @Getter
  @Builder
  public static class UploadedFileInfo {
    private final String id;
    private final String fileName;
    private final String contentType;
    private final long size;
    private final Path path;
  }
}
