package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.document.DocumentDTO;
import dev.lions.unionflow.server.api.dto.document.PieceJointeDTO;
import dev.lions.unionflow.server.entity.*;
import dev.lions.unionflow.server.repository.*;
import dev.lions.unionflow.server.service.KeycloakService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.NotFoundException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;

/**
 * Service métier pour la gestion documentaire
 *
 * @author UnionFlow Team
 * @version 3.0
 * @since 2025-01-29
 */
@ApplicationScoped
public class DocumentService {

  private static final Logger LOG = Logger.getLogger(DocumentService.class);

  @Inject DocumentRepository documentRepository;

  @Inject PieceJointeRepository pieceJointeRepository;

  @Inject MembreRepository membreRepository;

  @Inject OrganisationRepository organisationRepository;

  @Inject CotisationRepository cotisationRepository;

  @Inject AdhesionRepository adhesionRepository;

  @Inject DemandeAideRepository demandeAideRepository;

  @Inject TransactionWaveRepository transactionWaveRepository;

  @Inject KeycloakService keycloakService;

  /**
   * Crée un nouveau document
   *
   * @param documentDTO DTO du document à créer
   * @return DTO du document créé
   */
  @Transactional
  public DocumentDTO creerDocument(DocumentDTO documentDTO) {
    LOG.infof("Création d'un nouveau document: %s", documentDTO.getNomFichier());

    Document document = convertToEntity(documentDTO);
    document.setCreePar(keycloakService.getCurrentUserEmail());

    documentRepository.persist(document);
    LOG.infof("Document créé avec succès: ID=%s, Fichier=%s", document.getId(), document.getNomFichier());

    return convertToDTO(document);
  }

  /**
   * Trouve un document par son ID
   *
   * @param id ID du document
   * @return DTO du document
   */
  public DocumentDTO trouverParId(UUID id) {
    return documentRepository
        .findByIdOptional(id)
        .map(this::convertToDTO)
        .orElseThrow(() -> new NotFoundException("Document non trouvé avec l'ID: " + id));
  }

  /**
   * Enregistre un téléchargement de document
   *
   * @param id ID du document
   */
  @Transactional
  public void enregistrerTelechargement(UUID id) {
    Document document =
        documentRepository
            .findByIdOptional(id)
            .orElseThrow(() -> new NotFoundException("Document non trouvé avec l'ID: " + id));

    document.setNombreTelechargements(
        (document.getNombreTelechargements() != null ? document.getNombreTelechargements() : 0) + 1);
    document.setDateDernierTelechargement(LocalDateTime.now());
    document.setModifiePar(keycloakService.getCurrentUserEmail());

    documentRepository.persist(document);
  }

  /**
   * Crée une pièce jointe
   *
   * @param pieceJointeDTO DTO de la pièce jointe à créer
   * @return DTO de la pièce jointe créée
   */
  @Transactional
  public PieceJointeDTO creerPieceJointe(PieceJointeDTO pieceJointeDTO) {
    LOG.infof("Création d'une nouvelle pièce jointe");

    PieceJointe pieceJointe = convertToEntity(pieceJointeDTO);

    // Vérifier qu'une seule relation est renseignée
    if (!pieceJointe.isValide()) {
      throw new IllegalArgumentException("Une seule relation doit être renseignée pour une pièce jointe");
    }

    pieceJointe.setCreePar(keycloakService.getCurrentUserEmail());
    pieceJointeRepository.persist(pieceJointe);

    LOG.infof("Pièce jointe créée avec succès: ID=%s", pieceJointe.getId());
    return convertToDTO(pieceJointe);
  }

  /**
   * Liste toutes les pièces jointes d'un document
   *
   * @param documentId ID du document
   * @return Liste des pièces jointes
   */
  public List<PieceJointeDTO> listerPiecesJointesParDocument(UUID documentId) {
    return pieceJointeRepository.findByDocumentId(documentId).stream()
        .map(this::convertToDTO)
        .collect(Collectors.toList());
  }

  // ========================================
  // MÉTHODES PRIVÉES
  // ========================================

  /** Convertit une entité Document en DTO */
  private DocumentDTO convertToDTO(Document document) {
    if (document == null) {
      return null;
    }

    DocumentDTO dto = new DocumentDTO();
    dto.setId(document.getId());
    dto.setNomFichier(document.getNomFichier());
    dto.setNomOriginal(document.getNomOriginal());
    dto.setCheminStockage(document.getCheminStockage());
    dto.setTypeMime(document.getTypeMime());
    dto.setTailleOctets(document.getTailleOctets());
    dto.setTypeDocument(document.getTypeDocument());
    dto.setHashMd5(document.getHashMd5());
    dto.setHashSha256(document.getHashSha256());
    dto.setDescription(document.getDescription());
    dto.setNombreTelechargements(document.getNombreTelechargements());
    dto.setTailleFormatee(document.getTailleFormatee());
    dto.setDateCreation(document.getDateCreation());
    dto.setDateModification(document.getDateModification());
    dto.setActif(document.getActif());

    return dto;
  }

  /** Convertit un DTO en entité Document */
  private Document convertToEntity(DocumentDTO dto) {
    if (dto == null) {
      return null;
    }

    Document document = new Document();
    document.setNomFichier(dto.getNomFichier());
    document.setNomOriginal(dto.getNomOriginal());
    document.setCheminStockage(dto.getCheminStockage());
    document.setTypeMime(dto.getTypeMime());
    document.setTailleOctets(dto.getTailleOctets());
    document.setTypeDocument(dto.getTypeDocument() != null ? dto.getTypeDocument() : dev.lions.unionflow.server.api.enums.document.TypeDocument.AUTRE);
    document.setHashMd5(dto.getHashMd5());
    document.setHashSha256(dto.getHashSha256());
    document.setDescription(dto.getDescription());
    document.setNombreTelechargements(dto.getNombreTelechargements() != null ? dto.getNombreTelechargements() : 0);

    return document;
  }

  /** Convertit une entité PieceJointe en DTO */
  private PieceJointeDTO convertToDTO(PieceJointe pieceJointe) {
    if (pieceJointe == null) {
      return null;
    }

    PieceJointeDTO dto = new PieceJointeDTO();
    dto.setId(pieceJointe.getId());
    dto.setOrdre(pieceJointe.getOrdre());
    dto.setLibelle(pieceJointe.getLibelle());
    dto.setCommentaire(pieceJointe.getCommentaire());

    if (pieceJointe.getDocument() != null) {
      dto.setDocumentId(pieceJointe.getDocument().getId());
    }
    if (pieceJointe.getMembre() != null) {
      dto.setMembreId(pieceJointe.getMembre().getId());
    }
    if (pieceJointe.getOrganisation() != null) {
      dto.setOrganisationId(pieceJointe.getOrganisation().getId());
    }
    if (pieceJointe.getCotisation() != null) {
      dto.setCotisationId(pieceJointe.getCotisation().getId());
    }
    if (pieceJointe.getAdhesion() != null) {
      dto.setAdhesionId(pieceJointe.getAdhesion().getId());
    }
    if (pieceJointe.getDemandeAide() != null) {
      dto.setDemandeAideId(pieceJointe.getDemandeAide().getId());
    }
    if (pieceJointe.getTransactionWave() != null) {
      dto.setTransactionWaveId(pieceJointe.getTransactionWave().getId());
    }

    dto.setDateCreation(pieceJointe.getDateCreation());
    dto.setDateModification(pieceJointe.getDateModification());
    dto.setActif(pieceJointe.getActif());

    return dto;
  }

  /** Convertit un DTO en entité PieceJointe */
  private PieceJointe convertToEntity(PieceJointeDTO dto) {
    if (dto == null) {
      return null;
    }

    PieceJointe pieceJointe = new PieceJointe();
    pieceJointe.setOrdre(dto.getOrdre() != null ? dto.getOrdre() : 1);
    pieceJointe.setLibelle(dto.getLibelle());
    pieceJointe.setCommentaire(dto.getCommentaire());

    // Relation Document
    if (dto.getDocumentId() != null) {
      Document document =
          documentRepository
              .findByIdOptional(dto.getDocumentId())
              .orElseThrow(() -> new NotFoundException("Document non trouvé avec l'ID: " + dto.getDocumentId()));
      pieceJointe.setDocument(document);
    }

    // Relations flexibles (une seule doit être renseignée)
    if (dto.getMembreId() != null) {
      Membre membre =
          membreRepository
              .findByIdOptional(dto.getMembreId())
              .orElseThrow(() -> new NotFoundException("Membre non trouvé avec l'ID: " + dto.getMembreId()));
      pieceJointe.setMembre(membre);
    }

    if (dto.getOrganisationId() != null) {
      Organisation org =
          organisationRepository
              .findByIdOptional(dto.getOrganisationId())
              .orElseThrow(
                  () ->
                      new NotFoundException(
                          "Organisation non trouvée avec l'ID: " + dto.getOrganisationId()));
      pieceJointe.setOrganisation(org);
    }

    if (dto.getCotisationId() != null) {
      Cotisation cotisation =
          cotisationRepository
              .findByIdOptional(dto.getCotisationId())
              .orElseThrow(
                  () -> new NotFoundException("Cotisation non trouvée avec l'ID: " + dto.getCotisationId()));
      pieceJointe.setCotisation(cotisation);
    }

    if (dto.getAdhesionId() != null) {
      Adhesion adhesion =
          adhesionRepository
              .findByIdOptional(dto.getAdhesionId())
              .orElseThrow(
                  () -> new NotFoundException("Adhésion non trouvée avec l'ID: " + dto.getAdhesionId()));
      pieceJointe.setAdhesion(adhesion);
    }

    if (dto.getDemandeAideId() != null) {
      DemandeAide demandeAide =
          demandeAideRepository
              .findByIdOptional(dto.getDemandeAideId())
              .orElseThrow(
                  () ->
                      new NotFoundException(
                          "Demande d'aide non trouvée avec l'ID: " + dto.getDemandeAideId()));
      pieceJointe.setDemandeAide(demandeAide);
    }

    if (dto.getTransactionWaveId() != null) {
      TransactionWave transactionWave =
          transactionWaveRepository
              .findByIdOptional(dto.getTransactionWaveId())
              .orElseThrow(
                  () ->
                      new NotFoundException(
                          "Transaction Wave non trouvée avec l'ID: " + dto.getTransactionWaveId()));
      pieceJointe.setTransactionWave(transactionWave);
    }

    return pieceJointe;
  }
}

