package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.admin.request.CreateAuditLogRequest;
import dev.lions.unionflow.server.api.dto.admin.response.AuditLogResponse;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.TestTransaction;
import jakarta.inject.Inject;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

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

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

@QuarkusTest
class AuditServiceTest {

    @Inject
    AuditService auditService;

    @Test
    @TestTransaction
    @DisplayName("enregistrerLog crée un log et retourne un DTO")
    void enregistrerLog_createsAndReturnsDto() {
        CreateAuditLogRequest request = CreateAuditLogRequest.builder()
                .typeAction("TEST_ACTION")
                .severite("INFO")
                .utilisateur("test@test.com")
                .module("TEST")
                .description("Log de test service")
                .dateHeure(LocalDateTime.now())
                .build();

        AuditLogResponse response = auditService.enregistrerLog(request);
        assertThat(response).isNotNull();
        assertThat(response.getId()).isNotNull();
        assertThat(response.getTypeAction()).isEqualTo("TEST_ACTION");
        assertThat(response.getSeverite()).isEqualTo("INFO");
    }

    @Test
    @TestTransaction
    @DisplayName("listerTous retourne une structure paginée")
    void listerTous_returnsPagedStructure() {
        Map<String, Object> result = auditService.listerTous(0, 10, "dateHeure", "desc");
        assertThat(result).containsKeys("data", "total", "page", "size", "totalPages");
        assertThat(result.get("data")).isInstanceOf(List.class);
        assertThat(result.get("page")).isEqualTo(0);
        assertThat(result.get("size")).isEqualTo(10);
    }

    @Test
    @TestTransaction
    @DisplayName("rechercher avec filtres null retourne une structure paginée")
    void rechercher_withNullFilters_returnsPagedStructure() {
        Map<String, Object> result = auditService.rechercher(
                null, null, null, null, null, null, null,
                0, 5);
        assertThat(result).containsKeys("data", "total", "page", "size", "totalPages");
    }

    @Test
    @TestTransaction
    @DisplayName("getStatistiques retourne total, success, errors, warnings")
    void getStatistiques_returnsStats() {
        Map<String, Object> stats = auditService.getStatistiques();
        assertThat(stats).containsKeys("total", "success", "errors", "warnings");
    }

    // ─────────────────────────────────────────────────────────────────────────
    // rechercher() — filtres individuels
    // ─────────────────────────────────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("rechercher avec dateDebut seul")
    void rechercher_avecDateDebut_seulementBrancheDateDebut() {
        auditService.enregistrerLog(CreateAuditLogRequest.builder()
                .typeAction("DATE_DEBUT_TEST").severite("INFO")
                .utilisateur("user@test.com").module("TEST")
                .description("Log pour test dateDebut").dateHeure(LocalDateTime.now())
                .build());

        LocalDateTime debut = LocalDateTime.now().minusDays(1);
        Map<String, Object> result = auditService.rechercher(
                debut, null, null, null, null, null, null, 0, 10);
        assertThat(result).containsKeys("data", "total", "page", "size", "totalPages");
    }

    @Test
    @TestTransaction
    @DisplayName("rechercher avec dateFin seul — branche dateFin non-null couverte")
    void rechercher_avecDateFin_seulementBrancheDateFin() {
        LocalDateTime fin = LocalDateTime.now().plusDays(1);
        Map<String, Object> result = auditService.rechercher(
                null, fin, null, null, null, null, null, 0, 10);
        assertThat(result).containsKeys("data", "total", "page", "size", "totalPages");
    }

    @Test
    @TestTransaction
    @DisplayName("rechercher avec typeAction non vide — branche typeAction non-null/non-vide couverte")
    void rechercher_avecTypeActionNonVide_brancheTrue() {
        auditService.enregistrerLog(CreateAuditLogRequest.builder()
                .typeAction("TYPE_ACTION_FILTRE").severite("INFO")
                .utilisateur("user@test.com").module("TEST")
                .description("Log typeAction").dateHeure(LocalDateTime.now())
                .build());

        Map<String, Object> result = auditService.rechercher(
                null, null, "TYPE_ACTION_FILTRE", null, null, null, null, 0, 10);
        assertThat(result).containsKeys("data", "total");
        assertThat(((List<?>) result.get("data"))).isNotEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("rechercher avec typeAction vide — branche typeAction isEmpty → false, prédicat non ajouté")
    void rechercher_avecTypeActionVide_brancheFalse() {
        Map<String, Object> result = auditService.rechercher(
                null, null, "", null, null, null, null, 0, 10);
        assertThat(result).containsKeys("data", "total", "page", "size", "totalPages");
    }

    @Test
    @TestTransaction
    @DisplayName("rechercher avec severite non vide — branche severite non-null/non-vide couverte")
    void rechercher_avecSeveriteNonVide_brancheTrue() {
        auditService.enregistrerLog(CreateAuditLogRequest.builder()
                .typeAction("SEVERITE_TEST").severite("WARNING")
                .utilisateur("user@test.com").module("TEST")
                .description("Log severite").dateHeure(LocalDateTime.now())
                .build());

        Map<String, Object> result = auditService.rechercher(
                null, null, null, "WARNING", null, null, null, 0, 10);
        assertThat(result).containsKeys("data", "total");
        assertThat(((List<?>) result.get("data"))).isNotEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("rechercher avec severite vide — branche severite isEmpty → false, prédicat non ajouté")
    void rechercher_avecSeveriteVide_brancheFalse() {
        Map<String, Object> result = auditService.rechercher(
                null, null, null, "", null, null, null, 0, 10);
        assertThat(result).containsKeys("data", "total", "page", "size", "totalPages");
    }

    @Test
    @TestTransaction
    @DisplayName("rechercher avec utilisateur non vide — branche utilisateur non-null/non-vide couverte")
    void rechercher_avecUtilisateurNonVide_brancheTrue() {
        auditService.enregistrerLog(CreateAuditLogRequest.builder()
                .typeAction("UTILISATEUR_TEST").severite("INFO")
                .utilisateur("utilisateur.recherche@test.com").module("TEST")
                .description("Log utilisateur").dateHeure(LocalDateTime.now())
                .build());

        Map<String, Object> result = auditService.rechercher(
                null, null, null, null, "utilisateur.recherche@test.com", null, null, 0, 10);
        assertThat(result).containsKeys("data", "total");
        assertThat(((List<?>) result.get("data"))).isNotEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("rechercher avec utilisateur vide — branche utilisateur isEmpty → false, prédicat non ajouté")
    void rechercher_avecUtilisateurVide_brancheFalse() {
        Map<String, Object> result = auditService.rechercher(
                null, null, null, null, "", null, null, 0, 10);
        assertThat(result).containsKeys("data", "total", "page", "size", "totalPages");
    }

    @Test
    @TestTransaction
    @DisplayName("rechercher avec module non vide — branche module non-null/non-vide couverte")
    void rechercher_avecModuleNonVide_brancheTrue() {
        auditService.enregistrerLog(CreateAuditLogRequest.builder()
                .typeAction("MODULE_TEST").severite("INFO")
                .utilisateur("user@test.com").module("MODULE_FILTRE")
                .description("Log module").dateHeure(LocalDateTime.now())
                .build());

        Map<String, Object> result = auditService.rechercher(
                null, null, null, null, null, "MODULE_FILTRE", null, 0, 10);
        assertThat(result).containsKeys("data", "total");
        assertThat(((List<?>) result.get("data"))).isNotEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("rechercher avec module vide — branche module isEmpty → false, prédicat non ajouté")
    void rechercher_avecModuleVide_brancheFalse() {
        Map<String, Object> result = auditService.rechercher(
                null, null, null, null, null, "", null, 0, 10);
        assertThat(result).containsKeys("data", "total", "page", "size", "totalPages");
    }

    @Test
    @TestTransaction
    @DisplayName("rechercher avec ipAddress non vide — branche ipAddress non-null/non-vide couverte")
    void rechercher_avecIpAddressNonVide_brancheTrue() {
        auditService.enregistrerLog(CreateAuditLogRequest.builder()
                .typeAction("IP_TEST").severite("INFO")
                .utilisateur("user@test.com").module("TEST")
                .ipAddress("192.168.1.100")
                .description("Log ipAddress").dateHeure(LocalDateTime.now())
                .build());

        Map<String, Object> result = auditService.rechercher(
                null, null, null, null, null, null, "192.168.1.100", 0, 10);
        assertThat(result).containsKeys("data", "total");
        assertThat(((List<?>) result.get("data"))).isNotEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("rechercher avec ipAddress vide — branche ipAddress isEmpty → false, prédicat non ajouté")
    void rechercher_avecIpAddressVide_brancheFalse() {
        Map<String, Object> result = auditService.rechercher(
                null, null, null, null, null, null, "", 0, 10);
        assertThat(result).containsKeys("data", "total", "page", "size", "totalPages");
    }

    // ── Branches manquantes : logLcbFtSeuilAtteint et listerTous ─────────────

    @Test
    @TestTransaction
    @DisplayName("logLcbFtSeuilAtteint: organisationId null + montant non-null + origineFonds non-null → branche organisationId null")
    void logLcbFtSeuilAtteint_orgNull_montantNonNull_origineNonNull() {
        // organisationId=null → branche false de if(organisationId != null)
        // montant != null → branche ternaire true
        // origineFonds != null → branche ternaire true
        auditService.logLcbFtSeuilAtteint(
                null, "operateur@test.com", "compte-123", "tx-456",
                BigDecimal.valueOf(15000000), "SALAIRE");
        // Pas d'exception → test passe
    }

    @Test
    @TestTransaction
    @DisplayName("logLcbFtSeuilAtteint: organisationId non-null + montant null + origineFonds null → couvre branches null")
    void logLcbFtSeuilAtteint_orgNonNull_montantNull_origineNull() {
        // organisationId=UUID.randomUUID() → branche true de if(organisationId != null)
        // montant=null → branche ternaire false → ""
        // origineFonds=null → branche ternaire false → ""
        auditService.logLcbFtSeuilAtteint(
                UUID.randomUUID(), "operateur2@test.com", "compte-789", "tx-012",
                null, null);
        // Pas d'exception → test passe
    }

    @Test
    @TestTransaction
    @DisplayName("listerTous: sortBy non-null + sortOrder null → branche sortBy non-null et sortOrder non-desc")
    void listerTous_sortByNonNull_sortOrderNull_returnsList() {
        // sortBy != null → orderBy = sortBy
        // sortOrder = null → !equalsIgnoreCase("desc") → "ASC"
        Map<String, Object> result = auditService.listerTous(0, 10, "typeAction", null);
        assertThat(result).containsKeys("data", "total", "page", "size", "totalPages");
    }

    @Test
    @TestTransaction
    @DisplayName("listerTous: sortBy null + sortOrder desc → branche sortBy null et sortOrder=desc")
    void listerTous_sortByNull_sortOrderDesc_returnsList() {
        // sortBy=null → orderBy = "dateHeure"
        // sortOrder="desc" → "DESC"
        Map<String, Object> result = auditService.listerTous(0, 10, null, "desc");
        assertThat(result).containsKeys("data", "total", "page", "size", "totalPages");
    }
}
