package dev.lions.unionflow.server.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import dev.lions.unionflow.server.api.dto.finance_workflow.request.CreateBudgetRequest;
import dev.lions.unionflow.server.api.dto.finance_workflow.request.CreateBudgetLineRequest;
import dev.lions.unionflow.server.api.dto.finance_workflow.response.BudgetResponse;
import dev.lions.unionflow.server.entity.Budget;
import dev.lions.unionflow.server.entity.BudgetLine;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.BudgetRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Optional;
import java.util.UUID;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
 * Tests complémentaires pour {@link BudgetService} — cas limites de createBudget
 * (currency null) et des méthodes privées calculateStartDate/calculateEndDate (month null).
 *
 * <p>Note : les méthodes privées sont testées directement par réflexion car la garde
 * {@code if ("MONTHLY" && month==null) throw BadRequestException} empêche d'y accéder via createBudget.
 */
@QuarkusTest
@DisplayName("BudgetService — cas limites")
class BudgetServiceCoverageTest {

    @Inject
    BudgetService budgetService;

    @InjectMock
    BudgetRepository budgetRepository;

    @InjectMock
    OrganisationRepository organisationRepository;

    @InjectMock
    JsonWebToken jwt;

    private final UUID ORG_ID = UUID.randomUUID();
    private final UUID USER_ID = UUID.randomUUID();
    private Organisation org;

    @BeforeEach
    void setup() {
        org = new Organisation();
        org.setId(ORG_ID);
        when(jwt.<String>getClaim("sub")).thenReturn(USER_ID.toString());
    }

    // =========================================================================
    // createBudget — currency null → "XOF" (branche ternaire ligne 110)
    // =========================================================================

    @Test
    @DisplayName("createBudget avec currency null → utilise 'XOF' par défaut (branche ternaire currency != null false)")
    void createBudget_currencyNull_utiliseCurrencyXofParDefaut() {
        when(organisationRepository.findByIdOptional(ORG_ID)).thenReturn(Optional.of(org));

        CreateBudgetRequest req = CreateBudgetRequest.builder()
                .name("Budget Sans Devise")
                .organizationId(ORG_ID)
                .period("ANNUAL")
                .year(2026)
                .currency(null)  // null → branche ternaire: currency != null = false → "XOF"
                .build();

        BudgetResponse result = budgetService.createBudget(req);

        assertThat(result).isNotNull();
        assertThat(result.getCurrency()).isEqualTo("XOF");
        verify(budgetRepository).persist(any(Budget.class));
    }

    // =========================================================================
    // calculateStartDate — MONTHLY avec month==null → mois=1 (ternaire)
    // =========================================================================

    @Test
    @DisplayName("calculateStartDate MONTHLY month==null → mois=1 par défaut (branche ternaire)")
    void calculateStartDate_monthlyMonthNull_utiliseMoisUn() throws Exception {
        Method m = BudgetService.class.getDeclaredMethod(
                "calculateStartDate", String.class, int.class, Integer.class);
        m.setAccessible(true);

        // month==null → ternaire: month != null ? month : 1 → mois=1
        LocalDate result = (LocalDate) m.invoke(budgetService, "MONTHLY", 2026, null);

        assertThat(result).isEqualTo(LocalDate.of(2026, 1, 1));
    }

    @Test
    @DisplayName("calculateStartDate MONTHLY month non null → utilise le mois fourni")
    void calculateStartDate_monthlyMonthNonNull_utiliseMoisFourni() throws Exception {
        Method m = BudgetService.class.getDeclaredMethod(
                "calculateStartDate", String.class, int.class, Integer.class);
        m.setAccessible(true);

        LocalDate result = (LocalDate) m.invoke(budgetService, "MONTHLY", 2026, 6);

        assertThat(result).isEqualTo(LocalDate.of(2026, 6, 1));
    }

    // =========================================================================
    // calculateEndDate — MONTHLY avec month==null → mois=1 (ternaire)
    // =========================================================================

    @Test
    @DisplayName("calculateEndDate MONTHLY month==null → mois=1, fin = 31 janvier (branche ternaire)")
    void calculateEndDate_monthlyMonthNull_utiliseMoisUn() throws Exception {
        Method m = BudgetService.class.getDeclaredMethod(
                "calculateEndDate", String.class, int.class, Integer.class);
        m.setAccessible(true);

        // month==null → int m = month != null ? month : 1 → m=1 → LocalDate(2026,1,1).plusMonths(1).minusDays(1) = 31 jan
        LocalDate result = (LocalDate) m.invoke(budgetService, "MONTHLY", 2026, null);

        assertThat(result).isEqualTo(LocalDate.of(2026, 1, 31));
    }

    @Test
    @DisplayName("calculateEndDate MONTHLY month non null → fin du mois correct")
    void calculateEndDate_monthlyMonthNonNull_finDuMoisCorrect() throws Exception {
        Method m = BudgetService.class.getDeclaredMethod(
                "calculateEndDate", String.class, int.class, Integer.class);
        m.setAccessible(true);

        // month=2, année 2026 → 28 février 2026 (non bissextile)
        LocalDate result = (LocalDate) m.invoke(budgetService, "MONTHLY", 2026, 2);

        assertThat(result).isEqualTo(LocalDate.of(2026, 2, 28));
    }

    // =========================================================================
    // toResponse — varianceRate : totalPlanned=0 → 0.0 (pas de division par zéro)
    // =========================================================================

    @Test
    @DisplayName("varianceRate=0.0 quand totalPlanned=0 (guard BigDecimal.ZERO.compareTo)")
    void toResponse_totalPlannedZero_varianceRateIsZero() {
        // Budget sans ligne → totalPlanned=0, totalRealized=0
        Budget b = Budget.builder()
                .name("Budget Vide")
                .organisation(org)
                .period("ANNUAL")
                .year(2026)
                .currency("XOF")
                .createdById(USER_ID)
                .createdAtBudget(LocalDateTime.now())
                .startDate(LocalDate.of(2026, 1, 1))
                .endDate(LocalDate.of(2026, 12, 31))
                .build();
        b.setId(UUID.randomUUID());
        when(budgetRepository.findByIdOptional(b.getId())).thenReturn(Optional.of(b));

        BudgetResponse response = budgetService.getBudgetById(b.getId());

        assertThat(response.getTotalPlanned()).isEqualByComparingTo(BigDecimal.ZERO);
        assertThat(response.getVarianceRate()).isEqualTo(0.0);
    }

    @Test
    @DisplayName("varianceRate calculé correctement quand totalPlanned > 0")
    void toResponse_totalPlannedPositive_varianceRateCalculated() {
        // Budget avec totalPlanned=500000, totalRealized=300000
        // variance = 300000 - 500000 = -200000 → varianceRate = -200000/500000*100 = -40.0%
        Budget b = Budget.builder()
                .name("Budget avec lignes")
                .organisation(org)
                .period("ANNUAL")
                .year(2026)
                .currency("XOF")
                .createdById(USER_ID)
                .createdAtBudget(LocalDateTime.now())
                .startDate(LocalDate.of(2026, 1, 1))
                .endDate(LocalDate.of(2026, 12, 31))
                .build();
        b.setId(UUID.randomUUID());

        BudgetLine line = BudgetLine.builder()
                .budget(b)
                .category("CONTRIBUTIONS")
                .name("Cotisations")
                .amountPlanned(BigDecimal.valueOf(500_000))
                .amountRealized(BigDecimal.valueOf(300_000))
                .build();
        line.setId(UUID.randomUUID());
        b.addLine(line);

        when(budgetRepository.findByIdOptional(b.getId())).thenReturn(Optional.of(b));

        BudgetResponse response = budgetService.getBudgetById(b.getId());

        assertThat(response.getTotalPlanned()).isEqualByComparingTo(BigDecimal.valueOf(500_000));
        assertThat(response.getVarianceRate()).isEqualTo(-40.0);
    }
}
