package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.comptabilite.*;
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.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;

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

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

  @Inject CompteComptableRepository compteComptableRepository;

  @Inject JournalComptableRepository journalComptableRepository;

  @Inject EcritureComptableRepository ecritureComptableRepository;

  @Inject LigneEcritureRepository ligneEcritureRepository;

  @Inject OrganisationRepository organisationRepository;

  @Inject PaiementRepository paiementRepository;

  @Inject KeycloakService keycloakService;

  // ========================================
  // COMPTES COMPTABLES
  // ========================================

  /**
   * Crée un nouveau compte comptable
   *
   * @param compteDTO DTO du compte à créer
   * @return DTO du compte créé
   */
  @Transactional
  public CompteComptableDTO creerCompteComptable(CompteComptableDTO compteDTO) {
    LOG.infof("Création d'un nouveau compte comptable: %s", compteDTO.getNumeroCompte());

    // Vérifier l'unicité du numéro
    if (compteComptableRepository.findByNumeroCompte(compteDTO.getNumeroCompte()).isPresent()) {
      throw new IllegalArgumentException("Un compte avec ce numéro existe déjà: " + compteDTO.getNumeroCompte());
    }

    CompteComptable compte = convertToEntity(compteDTO);
    compte.setCreePar(keycloakService.getCurrentUserEmail());

    compteComptableRepository.persist(compte);
    LOG.infof("Compte comptable créé avec succès: ID=%s, Numéro=%s", compte.getId(), compte.getNumeroCompte());

    return convertToDTO(compte);
  }

  /**
   * Trouve un compte comptable par son ID
   *
   * @param id ID du compte
   * @return DTO du compte
   */
  public CompteComptableDTO trouverCompteParId(UUID id) {
    return compteComptableRepository
        .findByIdOptional(id)
        .map(this::convertToDTO)
        .orElseThrow(() -> new NotFoundException("Compte comptable non trouvé avec l'ID: " + id));
  }

  /**
   * Liste tous les comptes comptables actifs
   *
   * @return Liste des comptes
   */
  public List<CompteComptableDTO> listerTousLesComptes() {
    return compteComptableRepository.findAllActifs().stream()
        .map(this::convertToDTO)
        .collect(Collectors.toList());
  }

  // ========================================
  // JOURNAUX COMPTABLES
  // ========================================

  /**
   * Crée un nouveau journal comptable
   *
   * @param journalDTO DTO du journal à créer
   * @return DTO du journal créé
   */
  @Transactional
  public JournalComptableDTO creerJournalComptable(JournalComptableDTO journalDTO) {
    LOG.infof("Création d'un nouveau journal comptable: %s", journalDTO.getCode());

    // Vérifier l'unicité du code
    if (journalComptableRepository.findByCode(journalDTO.getCode()).isPresent()) {
      throw new IllegalArgumentException("Un journal avec ce code existe déjà: " + journalDTO.getCode());
    }

    JournalComptable journal = convertToEntity(journalDTO);
    journal.setCreePar(keycloakService.getCurrentUserEmail());

    journalComptableRepository.persist(journal);
    LOG.infof("Journal comptable créé avec succès: ID=%s, Code=%s", journal.getId(), journal.getCode());

    return convertToDTO(journal);
  }

  /**
   * Trouve un journal comptable par son ID
   *
   * @param id ID du journal
   * @return DTO du journal
   */
  public JournalComptableDTO trouverJournalParId(UUID id) {
    return journalComptableRepository
        .findByIdOptional(id)
        .map(this::convertToDTO)
        .orElseThrow(() -> new NotFoundException("Journal comptable non trouvé avec l'ID: " + id));
  }

  /**
   * Liste tous les journaux comptables actifs
   *
   * @return Liste des journaux
   */
  public List<JournalComptableDTO> listerTousLesJournaux() {
    return journalComptableRepository.findAllActifs().stream()
        .map(this::convertToDTO)
        .collect(Collectors.toList());
  }

  // ========================================
  // ÉCRITURES COMPTABLES
  // ========================================

  /**
   * Crée une nouvelle écriture comptable avec validation de l'équilibre
   *
   * @param ecritureDTO DTO de l'écriture à créer
   * @return DTO de l'écriture créée
   */
  @Transactional
  public EcritureComptableDTO creerEcritureComptable(EcritureComptableDTO ecritureDTO) {
    LOG.infof("Création d'une nouvelle écriture comptable: %s", ecritureDTO.getNumeroPiece());

    // Vérifier l'équilibre
    if (!isEcritureEquilibree(ecritureDTO)) {
      throw new IllegalArgumentException("L'écriture n'est pas équilibrée (Débit ≠ Crédit)");
    }

    EcritureComptable ecriture = convertToEntity(ecritureDTO);
    ecriture.setCreePar(keycloakService.getCurrentUserEmail());

    // Calculer les totaux
    ecriture.calculerTotaux();

    ecritureComptableRepository.persist(ecriture);
    LOG.infof("Écriture comptable créée avec succès: ID=%s, Numéro=%s", ecriture.getId(), ecriture.getNumeroPiece());

    return convertToDTO(ecriture);
  }

  /**
   * Trouve une écriture comptable par son ID
   *
   * @param id ID de l'écriture
   * @return DTO de l'écriture
   */
  public EcritureComptableDTO trouverEcritureParId(UUID id) {
    return ecritureComptableRepository
        .findByIdOptional(id)
        .map(this::convertToDTO)
        .orElseThrow(() -> new NotFoundException("Écriture comptable non trouvée avec l'ID: " + id));
  }

  /**
   * Liste les écritures d'un journal
   *
   * @param journalId ID du journal
   * @return Liste des écritures
   */
  public List<EcritureComptableDTO> listerEcrituresParJournal(UUID journalId) {
    return ecritureComptableRepository.findByJournalId(journalId).stream()
        .map(this::convertToDTO)
        .collect(Collectors.toList());
  }

  /**
   * Liste les écritures d'une organisation
   *
   * @param organisationId ID de l'organisation
   * @return Liste des écritures
   */
  public List<EcritureComptableDTO> listerEcrituresParOrganisation(UUID organisationId) {
    return ecritureComptableRepository.findByOrganisationId(organisationId).stream()
        .map(this::convertToDTO)
        .collect(Collectors.toList());
  }

  // ========================================
  // MÉTHODES PRIVÉES - CONVERSIONS
  // ========================================

  /** Vérifie si une écriture est équilibrée */
  private boolean isEcritureEquilibree(EcritureComptableDTO ecritureDTO) {
    if (ecritureDTO.getLignes() == null || ecritureDTO.getLignes().isEmpty()) {
      return false;
    }

    BigDecimal totalDebit =
        ecritureDTO.getLignes().stream()
            .map(LigneEcritureDTO::getMontantDebit)
            .filter(amount -> amount != null)
            .reduce(BigDecimal.ZERO, BigDecimal::add);

    BigDecimal totalCredit =
        ecritureDTO.getLignes().stream()
            .map(LigneEcritureDTO::getMontantCredit)
            .filter(amount -> amount != null)
            .reduce(BigDecimal.ZERO, BigDecimal::add);

    return totalDebit.compareTo(totalCredit) == 0;
  }

  /** Convertit une entité CompteComptable en DTO */
  private CompteComptableDTO convertToDTO(CompteComptable compte) {
    if (compte == null) {
      return null;
    }

    CompteComptableDTO dto = new CompteComptableDTO();
    dto.setId(compte.getId());
    dto.setNumeroCompte(compte.getNumeroCompte());
    dto.setLibelle(compte.getLibelle());
    dto.setTypeCompte(compte.getTypeCompte());
    dto.setClasseComptable(compte.getClasseComptable());
    dto.setSoldeInitial(compte.getSoldeInitial());
    dto.setSoldeActuel(compte.getSoldeActuel());
    dto.setCompteCollectif(compte.getCompteCollectif());
    dto.setCompteAnalytique(compte.getCompteAnalytique());
    dto.setDescription(compte.getDescription());
    dto.setDateCreation(compte.getDateCreation());
    dto.setDateModification(compte.getDateModification());
    dto.setActif(compte.getActif());

    return dto;
  }

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

    CompteComptable compte = new CompteComptable();
    compte.setNumeroCompte(dto.getNumeroCompte());
    compte.setLibelle(dto.getLibelle());
    compte.setTypeCompte(dto.getTypeCompte());
    compte.setClasseComptable(dto.getClasseComptable());
    compte.setSoldeInitial(dto.getSoldeInitial() != null ? dto.getSoldeInitial() : BigDecimal.ZERO);
    compte.setSoldeActuel(dto.getSoldeActuel() != null ? dto.getSoldeActuel() : dto.getSoldeInitial());
    compte.setCompteCollectif(dto.getCompteCollectif() != null ? dto.getCompteCollectif() : false);
    compte.setCompteAnalytique(dto.getCompteAnalytique() != null ? dto.getCompteAnalytique() : false);
    compte.setDescription(dto.getDescription());

    return compte;
  }

  /** Convertit une entité JournalComptable en DTO */
  private JournalComptableDTO convertToDTO(JournalComptable journal) {
    if (journal == null) {
      return null;
    }

    JournalComptableDTO dto = new JournalComptableDTO();
    dto.setId(journal.getId());
    dto.setCode(journal.getCode());
    dto.setLibelle(journal.getLibelle());
    dto.setTypeJournal(journal.getTypeJournal());
    dto.setDateDebut(journal.getDateDebut());
    dto.setDateFin(journal.getDateFin());
    dto.setStatut(journal.getStatut());
    dto.setDescription(journal.getDescription());
    dto.setDateCreation(journal.getDateCreation());
    dto.setDateModification(journal.getDateModification());
    dto.setActif(journal.getActif());

    return dto;
  }

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

    JournalComptable journal = new JournalComptable();
    journal.setCode(dto.getCode());
    journal.setLibelle(dto.getLibelle());
    journal.setTypeJournal(dto.getTypeJournal());
    journal.setDateDebut(dto.getDateDebut());
    journal.setDateFin(dto.getDateFin());
    journal.setStatut(dto.getStatut() != null ? dto.getStatut() : "OUVERT");
    journal.setDescription(dto.getDescription());

    return journal;
  }

  /** Convertit une entité EcritureComptable en DTO */
  private EcritureComptableDTO convertToDTO(EcritureComptable ecriture) {
    if (ecriture == null) {
      return null;
    }

    EcritureComptableDTO dto = new EcritureComptableDTO();
    dto.setId(ecriture.getId());
    dto.setNumeroPiece(ecriture.getNumeroPiece());
    dto.setDateEcriture(ecriture.getDateEcriture());
    dto.setLibelle(ecriture.getLibelle());
    dto.setReference(ecriture.getReference());
    dto.setLettrage(ecriture.getLettrage());
    dto.setPointe(ecriture.getPointe());
    dto.setMontantDebit(ecriture.getMontantDebit());
    dto.setMontantCredit(ecriture.getMontantCredit());
    dto.setCommentaire(ecriture.getCommentaire());

    if (ecriture.getJournal() != null) {
      dto.setJournalId(ecriture.getJournal().getId());
    }
    if (ecriture.getOrganisation() != null) {
      dto.setOrganisationId(ecriture.getOrganisation().getId());
    }
    if (ecriture.getPaiement() != null) {
      dto.setPaiementId(ecriture.getPaiement().getId());
    }

    // Convertir les lignes
    if (ecriture.getLignes() != null) {
      dto.setLignes(
          ecriture.getLignes().stream().map(this::convertToDTO).collect(Collectors.toList()));
    }

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

    return dto;
  }

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

    EcritureComptable ecriture = new EcritureComptable();
    ecriture.setNumeroPiece(dto.getNumeroPiece());
    ecriture.setDateEcriture(dto.getDateEcriture() != null ? dto.getDateEcriture() : LocalDate.now());
    ecriture.setLibelle(dto.getLibelle());
    ecriture.setReference(dto.getReference());
    ecriture.setLettrage(dto.getLettrage());
    ecriture.setPointe(dto.getPointe() != null ? dto.getPointe() : false);
    ecriture.setCommentaire(dto.getCommentaire());

    // Relations
    if (dto.getJournalId() != null) {
      JournalComptable journal =
          journalComptableRepository
              .findByIdOptional(dto.getJournalId())
              .orElseThrow(
                  () -> new NotFoundException("Journal comptable non trouvé avec l'ID: " + dto.getJournalId()));
      ecriture.setJournal(journal);
    }

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

    if (dto.getPaiementId() != null) {
      Paiement paiement =
          paiementRepository
              .findByIdOptional(dto.getPaiementId())
              .orElseThrow(
                  () -> new NotFoundException("Paiement non trouvé avec l'ID: " + dto.getPaiementId()));
      ecriture.setPaiement(paiement);
    }

    // Convertir les lignes
    if (dto.getLignes() != null) {
      for (LigneEcritureDTO ligneDTO : dto.getLignes()) {
        LigneEcriture ligne = convertToEntity(ligneDTO);
        ligne.setEcriture(ecriture);
        ecriture.getLignes().add(ligne);
      }
    }

    return ecriture;
  }

  /** Convertit une entité LigneEcriture en DTO */
  private LigneEcritureDTO convertToDTO(LigneEcriture ligne) {
    if (ligne == null) {
      return null;
    }

    LigneEcritureDTO dto = new LigneEcritureDTO();
    dto.setId(ligne.getId());
    dto.setNumeroLigne(ligne.getNumeroLigne());
    dto.setMontantDebit(ligne.getMontantDebit());
    dto.setMontantCredit(ligne.getMontantCredit());
    dto.setLibelle(ligne.getLibelle());
    dto.setReference(ligne.getReference());

    if (ligne.getEcriture() != null) {
      dto.setEcritureId(ligne.getEcriture().getId());
    }
    if (ligne.getCompteComptable() != null) {
      dto.setCompteComptableId(ligne.getCompteComptable().getId());
    }

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

    return dto;
  }

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

    LigneEcriture ligne = new LigneEcriture();
    ligne.setNumeroLigne(dto.getNumeroLigne());
    ligne.setMontantDebit(dto.getMontantDebit() != null ? dto.getMontantDebit() : BigDecimal.ZERO);
    ligne.setMontantCredit(dto.getMontantCredit() != null ? dto.getMontantCredit() : BigDecimal.ZERO);
    ligne.setLibelle(dto.getLibelle());
    ligne.setReference(dto.getReference());

    // Relation CompteComptable
    if (dto.getCompteComptableId() != null) {
      CompteComptable compte =
          compteComptableRepository
              .findByIdOptional(dto.getCompteComptableId())
              .orElseThrow(
                  () ->
                      new NotFoundException(
                          "Compte comptable non trouvé avec l'ID: " + dto.getCompteComptableId()));
      ligne.setCompteComptable(compte);
    }

    return ligne;
  }
}

