package dev.lions.unionflow.server.repository;

import dev.lions.unionflow.server.entity.SystemAlert;
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 SystemAlertRepositoryTest {

    @Inject
    SystemAlertRepository systemAlertRepository;

    private static SystemAlert newAlert(String level, boolean acknowledged) {
        SystemAlert alert = new SystemAlert();
        alert.setLevel(level);
        alert.setTitle("Alert titre " + UUID.randomUUID());
        alert.setMessage("Alert message " + UUID.randomUUID());
        alert.setAcknowledged(acknowledged);
        alert.setTimestamp(LocalDateTime.now());
        alert.setActif(true);
        // BaseEntity.onCreate() est écrasé par SystemAlert.onCreate() sans super.
        // Il faut setter manuellement les champs de BaseEntity.
        alert.setDateCreation(LocalDateTime.now());
        alert.setDateModification(LocalDateTime.now());
        alert.setCreePar("test");
        alert.setModifiePar("test");
        return alert;
    }

    @Test
    @TestTransaction
    @DisplayName("persist puis findById retrouve la SystemAlert")
    void persist_thenFindById_findsAlert() {
        SystemAlert alert = newAlert("WARNING", false);
        systemAlertRepository.persist(alert);
        assertThat(alert.getId()).isNotNull();
        SystemAlert found = systemAlertRepository.findById(alert.getId());
        assertThat(found).isNotNull();
        assertThat(found.getLevel()).isEqualTo("WARNING");
    }

    @Test
    @TestTransaction
    @DisplayName("findActiveAlerts retourne les alertes non acquittées")
    void findActiveAlerts_returnsNonAcknowledgedAlerts() {
        SystemAlert active = newAlert("ERROR", false);
        SystemAlert acked = newAlert("INFO", true);
        systemAlertRepository.persist(active);
        systemAlertRepository.persist(acked);

        List<SystemAlert> result = systemAlertRepository.findActiveAlerts();
        assertThat(result).isNotNull();
        assertThat(result).anyMatch(a -> a.getId().equals(active.getId()));
        assertThat(result).noneMatch(a -> a.getId().equals(acked.getId()));
    }

    @Test
    @TestTransaction
    @DisplayName("findActiveAlerts retourne une liste non nulle même si vide")
    void findActiveAlerts_emptyOnNoActiveAlerts_returnsNotNull() {
        List<SystemAlert> result = systemAlertRepository.findActiveAlerts();
        assertThat(result).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findAcknowledgedAlerts retourne les alertes acquittées")
    void findAcknowledgedAlerts_returnsAcknowledgedAlerts() {
        SystemAlert acked = newAlert("INFO", true);
        SystemAlert active = newAlert("WARNING", false);
        systemAlertRepository.persist(acked);
        systemAlertRepository.persist(active);

        List<SystemAlert> result = systemAlertRepository.findAcknowledgedAlerts();
        assertThat(result).isNotNull();
        assertThat(result).anyMatch(a -> a.getId().equals(acked.getId()));
        assertThat(result).noneMatch(a -> a.getId().equals(active.getId()));
    }

    @Test
    @TestTransaction
    @DisplayName("findByLevel retourne les alertes du niveau donné")
    void findByLevel_returnsMatchingAlerts() {
        SystemAlert alert = newAlert("CRITICAL", false);
        systemAlertRepository.persist(alert);

        List<SystemAlert> result = systemAlertRepository.findByLevel("CRITICAL");
        assertThat(result).isNotNull();
        assertThat(result).anyMatch(a -> a.getId().equals(alert.getId()));
    }

    @Test
    @TestTransaction
    @DisplayName("findByLevel retourne liste vide pour niveau inexistant")
    void findByLevel_unknownLevel_returnsEmpty() {
        List<SystemAlert> result = systemAlertRepository.findByLevel("INEXISTANT_" + UUID.randomUUID());
        assertThat(result).isNotNull();
        assertThat(result).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findCriticalUnacknowledged retourne les alertes critiques non acquittées")
    void findCriticalUnacknowledged_returnsCriticalActiveAlerts() {
        SystemAlert criticalActive = newAlert("CRITICAL", false);
        SystemAlert criticalAcked = newAlert("CRITICAL", true);
        SystemAlert warningActive = newAlert("WARNING", false);
        systemAlertRepository.persist(criticalActive);
        systemAlertRepository.persist(criticalAcked);
        systemAlertRepository.persist(warningActive);

        List<SystemAlert> result = systemAlertRepository.findCriticalUnacknowledged();
        assertThat(result).isNotNull();
        assertThat(result).anyMatch(a -> a.getId().equals(criticalActive.getId()));
        assertThat(result).noneMatch(a -> a.getId().equals(criticalAcked.getId()));
        assertThat(result).noneMatch(a -> a.getId().equals(warningActive.getId()));
    }

    @Test
    @TestTransaction
    @DisplayName("findBySource retourne les alertes de la source donnée")
    void findBySource_returnsMatchingAlerts() {
        String source = "CPU-" + UUID.randomUUID();
        SystemAlert alert = newAlert("WARNING", false);
        alert.setSource(source);
        systemAlertRepository.persist(alert);

        List<SystemAlert> result = systemAlertRepository.findBySource(source);
        assertThat(result).isNotNull();
        assertThat(result).anyMatch(a -> a.getId().equals(alert.getId()));
    }

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

    @Test
    @TestTransaction
    @DisplayName("acknowledgeAlert acquitte une alerte existante")
    void acknowledgeAlert_setsAcknowledgedFields() {
        SystemAlert alert = newAlert("ERROR", false);
        systemAlertRepository.persist(alert);
        UUID alertId = alert.getId();
        assertThat(alertId).isNotNull();

        systemAlertRepository.acknowledgeAlert(alertId, "admin@test.com");

        SystemAlert updated = systemAlertRepository.findById(alertId);
        assertThat(updated).isNotNull();
        assertThat(updated.getAcknowledged()).isTrue();
        assertThat(updated.getAcknowledgedBy()).isEqualTo("admin@test.com");
        assertThat(updated.getAcknowledgedAt()).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("acknowledgeAlert avec UUID inexistant ne lève pas d'exception")
    void acknowledgeAlert_unknownId_doesNotThrow() {
        // Should do nothing silently when alert not found
        systemAlertRepository.acknowledgeAlert(UUID.randomUUID(), "admin@test.com");
        // No exception expected
    }

    @Test
    @TestTransaction
    @DisplayName("countActive retourne le nombre d'alertes non acquittées")
    void countActive_returnsNonNegativeCount() {
        long countBefore = systemAlertRepository.countActive();
        SystemAlert alert = newAlert("WARNING", false);
        systemAlertRepository.persist(alert);
        long countAfter = systemAlertRepository.countActive();
        assertThat(countAfter).isGreaterThan(countBefore);
    }

    @Test
    @TestTransaction
    @DisplayName("countLast24h retourne un nombre >= 0")
    void countLast24h_returnsNonNegative() {
        SystemAlert alert = newAlert("INFO", false);
        alert.setTimestamp(LocalDateTime.now());
        systemAlertRepository.persist(alert);

        long count = systemAlertRepository.countLast24h();
        assertThat(count).isGreaterThanOrEqualTo(1L);
    }

    @Test
    @TestTransaction
    @DisplayName("countAcknowledgedLast24h retourne un nombre >= 0")
    void countAcknowledgedLast24h_returnsNonNegative() {
        SystemAlert alert = newAlert("INFO", true);
        alert.setTimestamp(LocalDateTime.now());
        systemAlertRepository.persist(alert);

        long count = systemAlertRepository.countAcknowledgedLast24h();
        assertThat(count).isGreaterThanOrEqualTo(1L);
    }

    @Test
    @TestTransaction
    @DisplayName("deleteOlderThan supprime les alertes plus anciennes que le seuil")
    void deleteOlderThan_deletesOldAlerts() {
        SystemAlert oldAlert = newAlert("DEBUG", false);
        oldAlert.setTimestamp(LocalDateTime.now().minusDays(10));
        systemAlertRepository.persist(oldAlert);

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

    @Test
    @TestTransaction
    @DisplayName("deleteOlderThan avec seuil dans le passé lointain ne supprime rien de récent")
    void deleteOlderThan_distantPastThreshold_doesNotDeleteRecent() {
        SystemAlert recentAlert = newAlert("INFO", false);
        recentAlert.setTimestamp(LocalDateTime.now());
        systemAlertRepository.persist(recentAlert);

        int deleted = systemAlertRepository.deleteOlderThan(LocalDateTime.now().minusYears(10));
        assertThat(deleted).isGreaterThanOrEqualTo(0);

        // Recent alert should still exist
        assertThat(systemAlertRepository.findById(recentAlert.getId())).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findByTimestampBetween retourne les alertes dans la plage de temps")
    void findByTimestampBetween_returnsAlertsInRange() {
        SystemAlert alert = newAlert("WARNING", false);
        alert.setTimestamp(LocalDateTime.now());
        systemAlertRepository.persist(alert);

        LocalDateTime start = LocalDateTime.now().minusMinutes(5);
        LocalDateTime end = LocalDateTime.now().plusMinutes(5);
        List<SystemAlert> result = systemAlertRepository.findByTimestampBetween(start, end);
        assertThat(result).isNotNull();
        assertThat(result).anyMatch(a -> a.getId().equals(alert.getId()));
    }

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