package dev.lions.unionflow.server.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

/**
 * Entité EcritureComptable pour les écritures comptables
 *
 * @author UnionFlow Team
 * @version 3.0
 * @since 2025-01-29
 */
@Entity
@Table(
    name = "ecritures_comptables",
    indexes = {
      @Index(name = "idx_ecriture_numero_piece", columnList = "numero_piece", unique = true),
      @Index(name = "idx_ecriture_date", columnList = "date_ecriture"),
      @Index(name = "idx_ecriture_journal", columnList = "journal_id"),
      @Index(name = "idx_ecriture_organisation", columnList = "organisation_id"),
      @Index(name = "idx_ecriture_paiement", columnList = "paiement_id")
    })
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class EcritureComptable extends BaseEntity {

  /** Numéro de pièce unique */
  @NotBlank
  @Column(name = "numero_piece", unique = true, nullable = false, length = 50)
  private String numeroPiece;

  /** Date de l'écriture */
  @NotNull
  @Column(name = "date_ecriture", nullable = false)
  private LocalDate dateEcriture;

  /** Libellé de l'écriture */
  @NotBlank
  @Column(name = "libelle", nullable = false, length = 500)
  private String libelle;

  /** Référence externe */
  @Column(name = "reference", length = 100)
  private String reference;

  /** Lettrage (pour rapprochement) */
  @Column(name = "lettrage", length = 20)
  private String lettrage;

  /** Pointage (pour rapprochement bancaire) */
  @Builder.Default
  @Column(name = "pointe", nullable = false)
  private Boolean pointe = false;

  /** Montant total débit (somme des lignes) */
  @Builder.Default
  @DecimalMin(value = "0.0")
  @Digits(integer = 12, fraction = 2)
  @Column(name = "montant_debit", precision = 14, scale = 2)
  private BigDecimal montantDebit = BigDecimal.ZERO;

  /** Montant total crédit (somme des lignes) */
  @Builder.Default
  @DecimalMin(value = "0.0")
  @Digits(integer = 12, fraction = 2)
  @Column(name = "montant_credit", precision = 14, scale = 2)
  private BigDecimal montantCredit = BigDecimal.ZERO;

  /** Commentaires */
  @Column(name = "commentaire", length = 1000)
  private String commentaire;

  // Relations
  @NotNull
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "journal_id", nullable = false)
  private JournalComptable journal;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "organisation_id")
  private Organisation organisation;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "paiement_id")
  private Paiement paiement;

  /** Lignes d'écriture */
  @JsonIgnore
  @OneToMany(mappedBy = "ecriture", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
  @Builder.Default
  private List<LigneEcriture> lignes = new ArrayList<>();

  /** Méthode métier pour vérifier l'équilibre (Débit = Crédit) */
  public boolean isEquilibree() {
    if (montantDebit == null || montantCredit == null) {
      return false;
    }
    return montantDebit.compareTo(montantCredit) == 0;
  }

  /** Méthode métier pour calculer les totaux à partir des lignes */
  public void calculerTotaux() {
    if (lignes == null || lignes.isEmpty()) {
      montantDebit = BigDecimal.ZERO;
      montantCredit = BigDecimal.ZERO;
      return;
    }

    montantDebit =
        lignes.stream()
            .map(LigneEcriture::getMontantDebit)
            .filter(amount -> amount != null)
            .reduce(BigDecimal.ZERO, BigDecimal::add);

    montantCredit =
        lignes.stream()
            .map(LigneEcriture::getMontantCredit)
            .filter(amount -> amount != null)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
  }

  /** Méthode métier pour générer un numéro de pièce unique */
  public static String genererNumeroPiece(String prefixe, LocalDate date) {
    return String.format(
        "%s-%04d%02d%02d-%012d",
        prefixe, date.getYear(), date.getMonthValue(), date.getDayOfMonth(),
        System.currentTimeMillis() % 1000000000000L);
  }

  /** Callback JPA avant la persistance */
  @PrePersist
  protected void onCreate() {
    super.onCreate();
    if (numeroPiece == null || numeroPiece.isEmpty()) {
      numeroPiece = genererNumeroPiece("ECR", dateEcriture != null ? dateEcriture : LocalDate.now());
    }
    if (dateEcriture == null) {
      dateEcriture = LocalDate.now();
    }
    if (montantDebit == null) {
      montantDebit = BigDecimal.ZERO;
    }
    if (montantCredit == null) {
      montantCredit = BigDecimal.ZERO;
    }
    if (pointe == null) {
      pointe = false;
    }
    // Calculer les totaux si les lignes sont déjà présentes
    if (lignes != null && !lignes.isEmpty()) {
      calculerTotaux();
    }
  }

  /** Callback JPA avant la mise à jour */
  @PreUpdate
  protected void onUpdate() {
    calculerTotaux();
  }
}

