package dev.lions.unionflow.server.entity;

import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

/**
 * Entité Budget
 *
 * Représente un budget prévisionnel (mensuel/trimestriel/annuel) avec suivi de réalisation.
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2026-03-13
 */
@Entity
@Table(name = "budgets", indexes = {
    @Index(name = "idx_budget_organisation", columnList = "organisation_id"),
    @Index(name = "idx_budget_status", columnList = "status"),
    @Index(name = "idx_budget_period", columnList = "period"),
    @Index(name = "idx_budget_year_month", columnList = "year, month"),
    @Index(name = "idx_budget_created_by", columnList = "created_by_id")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class Budget extends BaseEntity {

    /** Nom du budget */
    @NotBlank
    @Size(max = 200)
    @Column(name = "name", nullable = false, length = 200)
    private String name;

    /** Description optionnelle */
    @Size(max = 1000)
    @Column(name = "description", length = 1000)
    private String description;

    /** Organisation concernée */
    @NotNull
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "organisation_id", nullable = false)
    private Organisation organisation;

    /** Période (MONTHLY, QUARTERLY, SEMIANNUAL, ANNUAL) */
    @NotBlank
    @Pattern(regexp = "^(MONTHLY|QUARTERLY|SEMIANNUAL|ANNUAL)$")
    @Column(name = "period", nullable = false, length = 20)
    private String period;

    /** Année du budget */
    @NotNull
    @Min(value = 2020, message = "L'année doit être >= 2020")
    @Max(value = 2100, message = "L'année doit être <= 2100")
    @Column(name = "year", nullable = false)
    private Integer year;

    /** Mois (1-12) pour budget mensuel, null sinon */
    @Min(value = 1)
    @Max(value = 12)
    @Column(name = "month")
    private Integer month;

    /** Statut (DRAFT, ACTIVE, CLOSED, CANCELLED) */
    @NotBlank
    @Pattern(regexp = "^(DRAFT|ACTIVE|CLOSED|CANCELLED)$")
    @Builder.Default
    @Column(name = "status", nullable = false, length = 20)
    private String status = "DRAFT";

    /** Lignes budgétaires */
    @OneToMany(mappedBy = "budget", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
    @Builder.Default
    private List<BudgetLine> lines = new ArrayList<>();

    /** Total prévu (somme des montants prévus des lignes) */
    @NotNull
    @DecimalMin(value = "0.0")
    @Digits(integer = 14, fraction = 2)
    @Builder.Default
    @Column(name = "total_planned", nullable = false, precision = 16, scale = 2)
    private BigDecimal totalPlanned = BigDecimal.ZERO;

    /** Total réalisé (somme des montants réalisés des lignes) */
    @DecimalMin(value = "0.0")
    @Digits(integer = 14, fraction = 2)
    @Builder.Default
    @Column(name = "total_realized", nullable = false, precision = 16, scale = 2)
    private BigDecimal totalRealized = BigDecimal.ZERO;

    /** Code devise ISO 3 lettres */
    @NotBlank
    @Pattern(regexp = "^[A-Z]{3}$")
    @Builder.Default
    @Column(name = "currency", nullable = false, length = 3)
    private String currency = "XOF";

    /** ID du créateur du budget */
    @NotNull
    @Column(name = "created_by_id", nullable = false)
    private UUID createdById;

    /** Date de création */
    @NotNull
    @Column(name = "created_at_budget", nullable = false)
    private LocalDateTime createdAtBudget;

    /** Date d'approbation */
    @Column(name = "approved_at")
    private LocalDateTime approvedAt;

    /** ID de l'approbateur */
    @Column(name = "approved_by_id")
    private UUID approvedById;

    /** Date de début de la période budgétaire */
    @NotNull
    @Column(name = "start_date", nullable = false)
    private LocalDate startDate;

    /** Date de fin de la période budgétaire */
    @NotNull
    @Column(name = "end_date", nullable = false)
    private LocalDate endDate;

    /** Métadonnées additionnelles (JSON) */
    @Column(name = "metadata", columnDefinition = "TEXT")
    private String metadata;

    @PrePersist
    protected void onCreate() {
        super.onCreate();
        if (createdAtBudget == null) {
            createdAtBudget = LocalDateTime.now();
        }
        if (currency == null) {
            currency = "XOF";
        }
        if (status == null) {
            status = "DRAFT";
        }
        if (totalPlanned == null) {
            totalPlanned = BigDecimal.ZERO;
        }
        if (totalRealized == null) {
            totalRealized = BigDecimal.ZERO;
        }
    }

    /** Méthode métier pour ajouter une ligne budgétaire */
    public void addLine(BudgetLine line) {
        lines.add(line);
        line.setBudget(this);
        recalculateTotals();
    }

    /** Méthode métier pour supprimer une ligne budgétaire */
    public void removeLine(BudgetLine line) {
        lines.remove(line);
        line.setBudget(null);
        recalculateTotals();
    }

    /** Méthode métier pour recalculer les totaux */
    public void recalculateTotals() {
        this.totalPlanned = lines.stream()
            .map(BudgetLine::getAmountPlanned)
            .reduce(BigDecimal.ZERO, BigDecimal::add);

        this.totalRealized = lines.stream()
            .map(BudgetLine::getAmountRealized)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    /** Méthode métier pour calculer le taux de réalisation (%) */
    public double getRealizationRate() {
        if (totalPlanned.compareTo(BigDecimal.ZERO) == 0) {
            return 0.0;
        }
        return totalRealized.divide(totalPlanned, 4, java.math.RoundingMode.HALF_UP)
            .multiply(new BigDecimal("100"))
            .doubleValue();
    }

    /** Méthode métier pour calculer l'écart (réalisé - prévu) */
    public BigDecimal getVariance() {
        return totalRealized.subtract(totalPlanned);
    }

    /** Méthode métier pour vérifier si le budget est dépassé */
    public boolean isOverBudget() {
        return totalRealized.compareTo(totalPlanned) > 0;
    }

    /** Méthode métier pour vérifier si le budget est actif */
    public boolean isActive() {
        return "ACTIVE".equals(status);
    }

    /** Méthode métier pour vérifier si la période est en cours */
    public boolean isCurrentPeriod() {
        LocalDate now = LocalDate.now();
        return !now.isBefore(startDate) && !now.isAfter(endDate);
    }
}
