package dev.lions.unionflow.server.repository;

import dev.lions.unionflow.server.entity.SystemLog;
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.time.LocalDateTime;
import java.util.List;
import java.util.UUID;

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

@QuarkusTest
class SystemLogRepositoryTest {

    @Inject
    SystemLogRepository systemLogRepository;

    private static SystemLog newSystemLog(String level, String source) {
        SystemLog log = new SystemLog();
        log.setLevel(level);
        log.setSource(source);
        log.setMessage("Test message " + UUID.randomUUID());
        log.setTimestamp(LocalDateTime.now());
        log.setActif(true);
        return log;
    }

    @Test
    @TestTransaction
    @DisplayName("persist puis findById retrouve le SystemLog")
    void persist_thenFindById_findsLog() {
        SystemLog log = newSystemLog("ERROR", "Database");
        systemLogRepository.persist(log);
        assertThat(log.getId()).isNotNull();
        SystemLog found = systemLogRepository.findById(log.getId());
        assertThat(found).isNotNull();
        assertThat(found.getLevel()).isEqualTo("ERROR");
    }

    @Test
    @TestTransaction
    @DisplayName("findByLevel retourne les logs du niveau donné")
    void findByLevel_returnsMatchingLogs() {
        String level = "WARNING";
        SystemLog log = newSystemLog(level, "API");
        systemLogRepository.persist(log);

        List<SystemLog> result = systemLogRepository.findByLevel(level);
        assertThat(result).isNotNull();
        assertThat(result).anyMatch(l -> l.getId().equals(log.getId()));
    }

    @Test
    @TestTransaction
    @DisplayName("findByLevel retourne liste vide pour niveau inexistant")
    void findByLevel_unknownLevel_returnsEmptyList() {
        List<SystemLog> result = systemLogRepository.findByLevel("INEXISTANT_LEVEL_" + UUID.randomUUID());
        assertThat(result).isNotNull();
        assertThat(result).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findBySource retourne les logs de la source donnée")
    void findBySource_returnsMatchingLogs() {
        String source = "Source-" + UUID.randomUUID();
        SystemLog log = newSystemLog("INFO", source);
        systemLogRepository.persist(log);

        List<SystemLog> result = systemLogRepository.findBySource(source);
        assertThat(result).isNotNull();
        assertThat(result).anyMatch(l -> l.getId().equals(log.getId()));
    }

    @Test
    @TestTransaction
    @DisplayName("findBySource retourne liste vide pour source inexistante")
    void findBySource_unknownSource_returnsEmptyList() {
        List<SystemLog> result = systemLogRepository.findBySource("INEXISTANT_SOURCE_" + UUID.randomUUID());
        assertThat(result).isNotNull();
        assertThat(result).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findByLevelAndSource retourne les logs correspondant aux deux critères")
    void findByLevelAndSource_returnsMatchingLogs() {
        String source = "Source-" + UUID.randomUUID();
        String level = "CRITICAL";
        SystemLog log = newSystemLog(level, source);
        systemLogRepository.persist(log);

        List<SystemLog> result = systemLogRepository.findByLevelAndSource(level, source);
        assertThat(result).isNotNull();
        assertThat(result).anyMatch(l -> l.getId().equals(log.getId()));
    }

    @Test
    @TestTransaction
    @DisplayName("findByLevelAndSource retourne vide pour combinaison inconnue")
    void findByLevelAndSource_unknownCombination_returnsEmpty() {
        List<SystemLog> result = systemLogRepository.findByLevelAndSource(
                "UNKNOWN_" + UUID.randomUUID(), "UNKNOWN_" + UUID.randomUUID());
        assertThat(result).isNotNull();
        assertThat(result).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findByTimestampBetween retourne les logs dans la plage")
    void findByTimestampBetween_returnsLogsInRange() {
        SystemLog log = newSystemLog("DEBUG", "Auth");
        log.setTimestamp(LocalDateTime.now());
        systemLogRepository.persist(log);

        LocalDateTime start = LocalDateTime.now().minusMinutes(5);
        LocalDateTime end = LocalDateTime.now().plusMinutes(5);
        List<SystemLog> result = systemLogRepository.findByTimestampBetween(start, end);
        assertThat(result).isNotNull();
        assertThat(result).anyMatch(l -> l.getId().equals(log.getId()));
    }

    @Test
    @TestTransaction
    @DisplayName("findByTimestampBetween retourne vide pour plage future")
    void findByTimestampBetween_futureRange_returnsEmpty() {
        LocalDateTime start = LocalDateTime.now().plusDays(1);
        LocalDateTime end = LocalDateTime.now().plusDays(2);
        List<SystemLog> result = systemLogRepository.findByTimestampBetween(start, end);
        assertThat(result).isNotNull();
        assertThat(result).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("searchByText retourne les logs correspondant au texte")
    void searchByText_returnsMatchingLogs() {
        String uniqueText = "UniqueSearchText-" + UUID.randomUUID();
        SystemLog log = newSystemLog("INFO", "Cache");
        log.setMessage(uniqueText);
        systemLogRepository.persist(log);

        List<SystemLog> result = systemLogRepository.searchByText(uniqueText);
        assertThat(result).isNotNull();
        assertThat(result).anyMatch(l -> l.getId().equals(log.getId()));
    }

    @Test
    @TestTransaction
    @DisplayName("searchByText retourne vide pour texte inexistant")
    void searchByText_unknownText_returnsEmpty() {
        List<SystemLog> result = systemLogRepository.searchByText("TEXTE_INEXISTANT_" + UUID.randomUUID());
        assertThat(result).isNotNull();
        assertThat(result).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("search avec tous les filtres retourne une liste")
    void search_withAllFilters_returnsList() {
        String source = "Source-" + UUID.randomUUID();
        String level = "ERROR";
        String uniqueMsg = "SearchMsg-" + UUID.randomUUID();
        SystemLog log = newSystemLog(level, source);
        log.setMessage(uniqueMsg);
        log.setTimestamp(LocalDateTime.now());
        systemLogRepository.persist(log);

        LocalDateTime start = LocalDateTime.now().minusMinutes(5);
        LocalDateTime end = LocalDateTime.now().plusMinutes(5);

        List<SystemLog> result = systemLogRepository.search(level, source, uniqueMsg, start, end, 0, 10);
        assertThat(result).isNotNull();
        assertThat(result).anyMatch(l -> l.getId().equals(log.getId()));
    }

    @Test
    @TestTransaction
    @DisplayName("search sans filtres retourne une liste paginée")
    void search_noFilters_returnsList() {
        List<SystemLog> result = systemLogRepository.search(null, null, null, null, null, 0, 10);
        assertThat(result).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("search avec TOUS comme level et source ignore ces filtres")
    void search_withTousFilters_ignoresLevelAndSource() {
        List<SystemLog> result = systemLogRepository.search("TOUS", "TOUS", null, null, null, 0, 10);
        assertThat(result).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("search avec filtres partiels: seulement level")
    void search_withOnlyLevel_returnsList() {
        SystemLog log = newSystemLog("INFO", "System");
        systemLogRepository.persist(log);

        List<SystemLog> result = systemLogRepository.search("INFO", null, null, null, null, 0, 10);
        assertThat(result).isNotNull();
        assertThat(result).anyMatch(l -> "INFO".equals(l.getLevel()));
    }

    @Test
    @TestTransaction
    @DisplayName("search avec seulement start date retourne une liste")
    void search_withOnlyStartDate_returnsList() {
        SystemLog log = newSystemLog("DEBUG", "API");
        log.setTimestamp(LocalDateTime.now());
        systemLogRepository.persist(log);

        List<SystemLog> result = systemLogRepository.search(null, null, null, LocalDateTime.now().minusHours(1), null, 0, 10);
        assertThat(result).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("search avec seulement end date retourne une liste")
    void search_withOnlyEndDate_returnsList() {
        List<SystemLog> result = systemLogRepository.search(null, null, null, null, LocalDateTime.now().plusHours(1), 0, 10);
        assertThat(result).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("countByLevelLast24h retourne un nombre >= 0")
    void countByLevelLast24h_returnsNonNegative() {
        long count = systemLogRepository.countByLevelLast24h("ERROR");
        assertThat(count).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("countByLevelLast24h compte les logs récents du niveau donné")
    void countByLevelLast24h_countsRecentLogs() {
        SystemLog log = newSystemLog("CRITICAL", "Database");
        log.setTimestamp(LocalDateTime.now());
        systemLogRepository.persist(log);

        long count = systemLogRepository.countByLevelLast24h("CRITICAL");
        assertThat(count).isGreaterThanOrEqualTo(1L);
    }

    @Test
    @TestTransaction
    @DisplayName("deleteOlderThan supprime les logs anciens")
    void deleteOlderThan_deletesOldLogs() {
        SystemLog log = newSystemLog("INFO", "System");
        log.setTimestamp(LocalDateTime.now().minusDays(10));
        systemLogRepository.persist(log);

        int deleted = systemLogRepository.deleteOlderThan(LocalDateTime.now().minusDays(5));
        assertThat(deleted).isGreaterThanOrEqualTo(1);
    }

    @Test
    @TestTransaction
    @DisplayName("deleteOlderThan avec seuil futur ne supprime rien parmi les logs récents")
    void deleteOlderThan_futureDateDeletesNothingRecent() {
        SystemLog log = newSystemLog("DEBUG", "Cache");
        log.setTimestamp(LocalDateTime.now());
        systemLogRepository.persist(log);

        // Seuil dans le passé lointain ne devrait pas supprimer les logs récents
        int deleted = systemLogRepository.deleteOlderThan(LocalDateTime.now().minusYears(10));
        assertThat(deleted).isGreaterThanOrEqualTo(0);
    }

    // -----------------------------------------------------------------------
    // search() — branches manquantes : source seule, searchQuery seule,
    // level blanc (ignoré), source blank (ignorée)
    // -----------------------------------------------------------------------

    @Test
    @TestTransaction
    @DisplayName("search avec seulement source retourne une liste")
    void search_withOnlySource_returnsList() {
        String source = "SourceOnly-" + UUID.randomUUID();
        SystemLog log = newSystemLog("INFO", source);
        log.setTimestamp(LocalDateTime.now());
        systemLogRepository.persist(log);

        List<SystemLog> result = systemLogRepository.search(null, source, null, null, null, 0, 10);
        assertThat(result).isNotNull();
        assertThat(result).anyMatch(l -> source.equals(l.getSource()));
    }

    @Test
    @TestTransaction
    @DisplayName("search avec seulement searchQuery retourne une liste")
    void search_withOnlySearchQuery_returnsList() {
        String uniqueMsg = "QueryOnly-" + UUID.randomUUID();
        SystemLog log = newSystemLog("WARN", "SomeSource");
        log.setMessage(uniqueMsg);
        log.setTimestamp(LocalDateTime.now());
        systemLogRepository.persist(log);

        List<SystemLog> result = systemLogRepository.search(null, null, uniqueMsg, null, null, 0, 10);
        assertThat(result).isNotNull();
        assertThat(result).anyMatch(l -> l.getMessage().contains(uniqueMsg));
    }

    @Test
    @TestTransaction
    @DisplayName("search avec level blank (ignoré comme filtre) retourne une liste")
    void search_withBlankLevel_ignoresLevelFilter() {
        // level blank → condition (level != null && !level.isBlank()) = false → ignoré
        SystemLog log = newSystemLog("DEBUG", "BlankLevelSource");
        log.setTimestamp(LocalDateTime.now());
        systemLogRepository.persist(log);

        List<SystemLog> result = systemLogRepository.search("  ", null, null, null, null, 0, 10);
        assertThat(result).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("search avec source blank (ignorée comme filtre) retourne une liste")
    void search_withBlankSource_ignoresSourceFilter() {
        // source blank → condition (source != null && !source.isBlank()) = false → ignorée
        SystemLog log = newSystemLog("INFO", "BlankSourceTest");
        log.setTimestamp(LocalDateTime.now());
        systemLogRepository.persist(log);

        List<SystemLog> result = systemLogRepository.search(null, "   ", null, null, null, 0, 10);
        assertThat(result).isNotNull();
    }

    /**
     * L102 + L122 branch manquante : searchQuery != null mais isBlank() → false
     * → `if (searchQuery != null && !searchQuery.isBlank())` → false (searchQuery est "  ")
     */
    @Test
    @TestTransaction
    @DisplayName("search avec searchQuery blank (ignoré comme filtre) retourne une liste")
    void search_withBlankSearchQuery_ignoresSearchQueryFilter() {
        // searchQuery blank → !searchQuery.isBlank() est false → condition false → ignoré
        SystemLog log = newSystemLog("INFO", "BlankQuerySource");
        log.setTimestamp(LocalDateTime.now());
        systemLogRepository.persist(log);

        List<SystemLog> result = systemLogRepository.search(null, null, "   ", null, null, 0, 10);
        assertThat(result).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("search avec start et end retourne les logs dans la plage temporelle")
    void search_withStartAndEnd_returnsLogsInRange() {
        SystemLog log = newSystemLog("ERROR", "TimeRange");
        log.setTimestamp(LocalDateTime.now());
        systemLogRepository.persist(log);

        LocalDateTime start = LocalDateTime.now().minusMinutes(2);
        LocalDateTime end = LocalDateTime.now().plusMinutes(2);
        List<SystemLog> result = systemLogRepository.search(null, null, null, start, end, 0, 10);
        assertThat(result).isNotNull();
        assertThat(result).anyMatch(l -> l.getId().equals(log.getId()));
    }
}
