package dev.lions.unionflow.server.entity;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;

@DisplayName("PaiementObjet entity")
class PaiementObjetTest {

    // -------------------------------------------------------------------------
    // No-args constructor
    // -------------------------------------------------------------------------
    @Test
    @DisplayName("no-args constructor creates instance with null fields")
    void noArgsConstructor_createsInstanceWithNullFields() {
        PaiementObjet po = new PaiementObjet();

        assertThat(po).isNotNull();
        assertThat(po.getPaiement()).isNull();
        assertThat(po.getTypeObjetCible()).isNull();
        assertThat(po.getObjetCibleId()).isNull();
        assertThat(po.getMontantApplique()).isNull();
        assertThat(po.getDateApplication()).isNull();
        assertThat(po.getCommentaire()).isNull();
    }

    // -------------------------------------------------------------------------
    // Builder
    // -------------------------------------------------------------------------
    @Nested
    @DisplayName("Builder")
    class BuilderTests {

        @Test
        @DisplayName("builder sets all fields")
        void builder_setsAllFields() {
            Paiement paiement = new Paiement();
            UUID objetId = UUID.randomUUID();
            LocalDateTime now = LocalDateTime.now();

            PaiementObjet po = PaiementObjet.builder()
                    .paiement(paiement)
                    .typeObjetCible("COTISATION")
                    .objetCibleId(objetId)
                    .montantApplique(BigDecimal.valueOf(15000))
                    .dateApplication(now)
                    .commentaire("Application cotisation mensuelle")
                    .build();

            assertThat(po.getPaiement()).isSameAs(paiement);
            assertThat(po.getTypeObjetCible()).isEqualTo("COTISATION");
            assertThat(po.getObjetCibleId()).isEqualTo(objetId);
            assertThat(po.getMontantApplique()).isEqualByComparingTo("15000");
            assertThat(po.getDateApplication()).isEqualTo(now);
            assertThat(po.getCommentaire()).isEqualTo("Application cotisation mensuelle");
        }

        @Test
        @DisplayName("builder produces distinct instances")
        void builder_producesDistinctInstances() {
            PaiementObjet po1 = PaiementObjet.builder().typeObjetCible("ADHESION").build();
            PaiementObjet po2 = PaiementObjet.builder().typeObjetCible("EVENEMENT").build();

            assertThat(po1).isNotSameAs(po2);
            assertThat(po1.getTypeObjetCible()).isNotEqualTo(po2.getTypeObjetCible());
        }
    }

    // -------------------------------------------------------------------------
    // All-args constructor via setters
    // -------------------------------------------------------------------------
    @Test
    @DisplayName("all fields set via setters are accessible")
    void allFields_setViaSetters() {
        Paiement paiement = new Paiement();
        UUID objetId = UUID.randomUUID();
        LocalDateTime dt = LocalDateTime.of(2026, 3, 15, 9, 0);

        PaiementObjet po = new PaiementObjet();
        po.setPaiement(paiement);
        po.setTypeObjetCible("AIDE");
        po.setObjetCibleId(objetId);
        po.setMontantApplique(BigDecimal.valueOf(5000, 2));
        po.setDateApplication(dt);
        po.setCommentaire("Aide urgence");

        assertThat(po.getPaiement()).isSameAs(paiement);
        assertThat(po.getTypeObjetCible()).isEqualTo("AIDE");
        assertThat(po.getObjetCibleId()).isEqualTo(objetId);
        assertThat(po.getMontantApplique()).isEqualByComparingTo(BigDecimal.valueOf(5000, 2));
        assertThat(po.getDateApplication()).isEqualTo(dt);
        assertThat(po.getCommentaire()).isEqualTo("Aide urgence");
    }

    // -------------------------------------------------------------------------
    // Getters / Setters individual
    // -------------------------------------------------------------------------
    @Nested
    @DisplayName("Getters and Setters")
    class GettersSettersTests {

        @Test
        @DisplayName("setPaiement / getPaiement round-trips")
        void paiement_roundTrips() {
            Paiement paiement = new Paiement();
            PaiementObjet po = new PaiementObjet();
            po.setPaiement(paiement);
            assertThat(po.getPaiement()).isSameAs(paiement);
        }

        @Test
        @DisplayName("setTypeObjetCible / getTypeObjetCible round-trips")
        void typeObjetCible_roundTrips() {
            PaiementObjet po = new PaiementObjet();
            po.setTypeObjetCible("EVENEMENT");
            assertThat(po.getTypeObjetCible()).isEqualTo("EVENEMENT");
        }

        @Test
        @DisplayName("setObjetCibleId / getObjetCibleId round-trips")
        void objetCibleId_roundTrips() {
            UUID id = UUID.randomUUID();
            PaiementObjet po = new PaiementObjet();
            po.setObjetCibleId(id);
            assertThat(po.getObjetCibleId()).isEqualTo(id);
        }

        @Test
        @DisplayName("setMontantApplique / getMontantApplique round-trips")
        void montantApplique_roundTrips() {
            BigDecimal montant = new BigDecimal("12500.00");
            PaiementObjet po = new PaiementObjet();
            po.setMontantApplique(montant);
            assertThat(po.getMontantApplique()).isEqualByComparingTo(montant);
        }

        @Test
        @DisplayName("setDateApplication / getDateApplication round-trips")
        void dateApplication_roundTrips() {
            LocalDateTime dt = LocalDateTime.of(2026, 1, 1, 0, 0);
            PaiementObjet po = new PaiementObjet();
            po.setDateApplication(dt);
            assertThat(po.getDateApplication()).isEqualTo(dt);
        }

        @Test
        @DisplayName("setCommentaire / getCommentaire round-trips")
        void commentaire_roundTrips() {
            PaiementObjet po = new PaiementObjet();
            po.setCommentaire("Détails supplémentaires");
            assertThat(po.getCommentaire()).isEqualTo("Détails supplémentaires");
        }
    }

    // -------------------------------------------------------------------------
    // Various typeObjetCible values (polymorphic usage)
    // -------------------------------------------------------------------------
    @Test
    @DisplayName("typeObjetCible accepts all expected polymorphic types")
    void typeObjetCible_acceptsPolymorphicTypes() {
        String[] types = {"COTISATION", "ADHESION", "EVENEMENT", "AIDE"};
        for (String type : types) {
            PaiementObjet po = PaiementObjet.builder().typeObjetCible(type).build();
            assertThat(po.getTypeObjetCible()).isEqualTo(type);
        }
    }

    // -------------------------------------------------------------------------
    // UUID id (inherited from BaseEntity)
    // -------------------------------------------------------------------------
    @Test
    @DisplayName("UUID id field is settable and gettable")
    void uuidId_settableAndGettable() {
        UUID id = UUID.randomUUID();
        PaiementObjet po = new PaiementObjet();
        po.setId(id);
        assertThat(po.getId()).isEqualTo(id);
    }

    // -------------------------------------------------------------------------
    // equals / hashCode
    // -------------------------------------------------------------------------
    @Test
    @DisplayName("two instances with the same UUID id are equal")
    void equals_sameId_areEqual() {
        UUID id = UUID.randomUUID();
        PaiementObjet a = new PaiementObjet();
        a.setId(id);
        a.setTypeObjetCible("COTISATION");
        PaiementObjet b = new PaiementObjet();
        b.setId(id);
        b.setTypeObjetCible("COTISATION");

        assertThat(a).isEqualTo(b);
        assertThat(a.hashCode()).isEqualTo(b.hashCode());
    }

    @Test
    @DisplayName("two instances with different ids are not equal")
    void equals_differentIds_notEqual() {
        PaiementObjet a = new PaiementObjet();
        a.setId(UUID.randomUUID());
        PaiementObjet b = new PaiementObjet();
        b.setId(UUID.randomUUID());

        assertThat(a).isNotEqualTo(b);
    }
}
