package dev.lions.unionflow.server.entity;

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

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.UUID;

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

@DisplayName("TransactionApproval")
class TransactionApprovalTest {

    private static Organisation newOrganisation() {
        Organisation o = new Organisation();
        o.setId(UUID.randomUUID());
        return o;
    }

    private static ApproverAction approvedAction() {
        ApproverAction a = new ApproverAction();
        a.setApproverId(UUID.randomUUID());
        a.setApproverName("Approver Test");
        a.setApproverRole("TRESORIER");
        a.setDecision("APPROVED");
        return a;
    }

    private static ApproverAction pendingAction() {
        ApproverAction a = new ApproverAction();
        a.setApproverId(UUID.randomUUID());
        a.setApproverName("Approver Pending");
        a.setApproverRole("PRESIDENT");
        a.setDecision("PENDING");
        return a;
    }

    // -------------------------------------------------------------------------
    // getRequiredApprovals
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("getRequiredApprovals: NONE renvoie 0")
    void getRequiredApprovals_none_returns0() {
        TransactionApproval ta = new TransactionApproval();
        ta.setRequiredLevel("NONE");
        assertThat(ta.getRequiredApprovals()).isEqualTo(0);
    }

    @Test
    @DisplayName("getRequiredApprovals: LEVEL1 renvoie 1")
    void getRequiredApprovals_level1_returns1() {
        TransactionApproval ta = new TransactionApproval();
        ta.setRequiredLevel("LEVEL1");
        assertThat(ta.getRequiredApprovals()).isEqualTo(1);
    }

    @Test
    @DisplayName("getRequiredApprovals: LEVEL2 renvoie 2")
    void getRequiredApprovals_level2_returns2() {
        TransactionApproval ta = new TransactionApproval();
        ta.setRequiredLevel("LEVEL2");
        assertThat(ta.getRequiredApprovals()).isEqualTo(2);
    }

    @Test
    @DisplayName("getRequiredApprovals: LEVEL3 renvoie 3")
    void getRequiredApprovals_level3_returns3() {
        TransactionApproval ta = new TransactionApproval();
        ta.setRequiredLevel("LEVEL3");
        assertThat(ta.getRequiredApprovals()).isEqualTo(3);
    }

    @Test
    @DisplayName("getRequiredApprovals: valeur inconnue renvoie 0 (default)")
    void getRequiredApprovals_default_returns0() {
        TransactionApproval ta = new TransactionApproval();
        ta.setRequiredLevel("INVALID");
        assertThat(ta.getRequiredApprovals()).isEqualTo(0);
    }

    // -------------------------------------------------------------------------
    // countApprovals
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("countApprovals: aucun approbateur renvoie 0")
    void countApprovals_noApprovers_returns0() {
        TransactionApproval ta = TransactionApproval.builder()
            .transactionId(UUID.randomUUID())
            .transactionType("CONTRIBUTION")
            .amount(new BigDecimal("5000.00"))
            .requesterId(UUID.randomUUID())
            .requesterName("Test Requester")
            .requiredLevel("LEVEL1")
            .build();

        assertThat(ta.countApprovals()).isEqualTo(0);
    }

    @Test
    @DisplayName("countApprovals: seuls les APPROVED sont comptés")
    void countApprovals_mixedDecisions_countsApprovedOnly() {
        TransactionApproval ta = TransactionApproval.builder()
            .transactionId(UUID.randomUUID())
            .transactionType("DEPOSIT")
            .amount(new BigDecimal("10000.00"))
            .requesterId(UUID.randomUUID())
            .requesterName("Requester")
            .requiredLevel("LEVEL2")
            .build();

        ta.addApproverAction(approvedAction());
        ta.addApproverAction(pendingAction());
        ta.addApproverAction(approvedAction());

        assertThat(ta.countApprovals()).isEqualTo(2);
    }

    // -------------------------------------------------------------------------
    // hasAllApprovals
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("hasAllApprovals: NONE sans approbateur renvoie toujours true")
    void hasAllApprovals_none_alwaysTrue() {
        TransactionApproval ta = new TransactionApproval();
        ta.setRequiredLevel("NONE");
        assertThat(ta.hasAllApprovals()).isTrue();
    }

    @Test
    @DisplayName("hasAllApprovals: LEVEL1 avec une approbation renvoie true")
    void hasAllApprovals_level1_oneApproval_true() {
        TransactionApproval ta = TransactionApproval.builder()
            .transactionId(UUID.randomUUID())
            .transactionType("CONTRIBUTION")
            .amount(new BigDecimal("1000.00"))
            .requesterId(UUID.randomUUID())
            .requesterName("Requester")
            .requiredLevel("LEVEL1")
            .build();

        ta.addApproverAction(approvedAction());

        assertThat(ta.hasAllApprovals()).isTrue();
    }

    @Test
    @DisplayName("hasAllApprovals: LEVEL2 avec une seule approbation renvoie false")
    void hasAllApprovals_level2_onlyOneApproval_false() {
        TransactionApproval ta = TransactionApproval.builder()
            .transactionId(UUID.randomUUID())
            .transactionType("WITHDRAWAL")
            .amount(new BigDecimal("50000.00"))
            .requesterId(UUID.randomUUID())
            .requesterName("Requester")
            .requiredLevel("LEVEL2")
            .build();

        ta.addApproverAction(approvedAction());

        assertThat(ta.hasAllApprovals()).isFalse();
    }

    // -------------------------------------------------------------------------
    // addApproverAction
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("addApproverAction: ajoute l'action et positionne le parent")
    void addApproverAction_setsParentAndAdds() {
        TransactionApproval ta = TransactionApproval.builder()
            .transactionId(UUID.randomUUID())
            .transactionType("CONTRIBUTION")
            .amount(new BigDecimal("2000.00"))
            .requesterId(UUID.randomUUID())
            .requesterName("Requester")
            .requiredLevel("LEVEL1")
            .build();

        ApproverAction action = pendingAction();
        ta.addApproverAction(action);

        assertThat(ta.getApprovers()).hasSize(1);
        assertThat(action.getApproval()).isSameAs(ta);
    }

    // -------------------------------------------------------------------------
    // isExpired
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("isExpired: expiresAt null renvoie false")
    void isExpired_expiresAtNull_returnsFalse() {
        TransactionApproval ta = new TransactionApproval();
        ta.setExpiresAt(null);
        assertThat(ta.isExpired()).isFalse();
    }

    @Test
    @DisplayName("isExpired: expiresAt dans le passé renvoie true")
    void isExpired_expiredInPast_returnsTrue() {
        TransactionApproval ta = new TransactionApproval();
        ta.setExpiresAt(LocalDateTime.now().minusMinutes(1));
        assertThat(ta.isExpired()).isTrue();
    }

    @Test
    @DisplayName("isExpired: expiresAt dans le futur renvoie false")
    void isExpired_futureExpiry_returnsFalse() {
        TransactionApproval ta = new TransactionApproval();
        ta.setExpiresAt(LocalDateTime.now().plusDays(1));
        assertThat(ta.isExpired()).isFalse();
    }

    // -------------------------------------------------------------------------
    // isPending
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("isPending: statut PENDING renvoie true")
    void isPending_pending_returnsTrue() {
        TransactionApproval ta = new TransactionApproval();
        ta.setStatus("PENDING");
        assertThat(ta.isPending()).isTrue();
    }

    @Test
    @DisplayName("isPending: statut APPROVED renvoie false")
    void isPending_approved_returnsFalse() {
        TransactionApproval ta = new TransactionApproval();
        ta.setStatus("APPROVED");
        assertThat(ta.isPending()).isFalse();
    }

    // -------------------------------------------------------------------------
    // isCompleted
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("isCompleted: statut VALIDATED renvoie true")
    void isCompleted_validated_returnsTrue() {
        TransactionApproval ta = new TransactionApproval();
        ta.setStatus("VALIDATED");
        assertThat(ta.isCompleted()).isTrue();
    }

    @Test
    @DisplayName("isCompleted: statut REJECTED renvoie true")
    void isCompleted_rejected_returnsTrue() {
        TransactionApproval ta = new TransactionApproval();
        ta.setStatus("REJECTED");
        assertThat(ta.isCompleted()).isTrue();
    }

    @Test
    @DisplayName("isCompleted: statut CANCELLED renvoie true")
    void isCompleted_cancelled_returnsTrue() {
        TransactionApproval ta = new TransactionApproval();
        ta.setStatus("CANCELLED");
        assertThat(ta.isCompleted()).isTrue();
    }

    @Test
    @DisplayName("isCompleted: statut PENDING renvoie false")
    void isCompleted_pending_returnsFalse() {
        TransactionApproval ta = new TransactionApproval();
        ta.setStatus("PENDING");
        assertThat(ta.isCompleted()).isFalse();
    }

    // -------------------------------------------------------------------------
    // onCreate (réflexion)
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("onCreate: initialise createdAt, currency, status, expiresAt si null")
    void onCreate_initializesNullFields() throws Exception {
        TransactionApproval ta = new TransactionApproval();
        ta.setCreatedAt(null);
        ta.setCurrency(null);
        ta.setStatus(null);
        ta.setExpiresAt(null);

        Method onCreate = TransactionApproval.class.getDeclaredMethod("onCreate");
        onCreate.setAccessible(true);
        onCreate.invoke(ta);

        assertThat(ta.getCreatedAt()).isNotNull();
        assertThat(ta.getCurrency()).isEqualTo("XOF");
        assertThat(ta.getStatus()).isEqualTo("PENDING");
        assertThat(ta.getExpiresAt()).isNotNull();
        // expiresAt doit être ~7 jours après createdAt
        assertThat(ta.getExpiresAt()).isAfter(ta.getCreatedAt());
        assertThat(ta.getExpiresAt()).isBeforeOrEqualTo(ta.getCreatedAt().plusDays(7).plusSeconds(1));
    }

    @Test
    @DisplayName("onCreate: ne remplace pas createdAt si déjà renseigné")
    void onCreate_doesNotOverrideExistingCreatedAt() throws Exception {
        LocalDateTime existingDate = LocalDateTime.of(2025, 3, 10, 9, 0);
        LocalDateTime existingExpiry = LocalDateTime.of(2025, 3, 17, 9, 0);

        TransactionApproval ta = new TransactionApproval();
        ta.setCreatedAt(existingDate);
        ta.setCurrency("EUR");
        ta.setStatus("APPROVED");
        ta.setExpiresAt(existingExpiry);

        Method onCreate = TransactionApproval.class.getDeclaredMethod("onCreate");
        onCreate.setAccessible(true);
        onCreate.invoke(ta);

        assertThat(ta.getCreatedAt()).isEqualTo(existingDate);
        assertThat(ta.getCurrency()).isEqualTo("EUR");
        assertThat(ta.getStatus()).isEqualTo("APPROVED");
        assertThat(ta.getExpiresAt()).isEqualTo(existingExpiry);
    }
}
