package dev.lions.unionflow.server.resource;

import static io.restassured.RestAssured.given;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

import dev.lions.unionflow.server.api.dto.notification.request.CreateNotificationRequest;
import dev.lions.unionflow.server.api.dto.notification.request.CreateTemplateNotificationRequest;
import dev.lions.unionflow.server.api.dto.notification.response.NotificationResponse;
import dev.lions.unionflow.server.api.dto.notification.response.TemplateNotificationResponse;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.service.NotificationService;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import io.restassured.http.ContentType;
import jakarta.ws.rs.NotFoundException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

/**
 * Tests d'intégration REST pour NotificationResource.
 *
 * <p>Utilise @InjectMock sur NotificationService et MembreRepository pour contrôler
 * tous les chemins de code sans base de données.
 *
 * @author UnionFlow Team
 */
@QuarkusTest
class NotificationResourceTest {

    private static final String BASE = "/api/notifications";

    @InjectMock
    NotificationService notificationService;

    @InjectMock
    MembreRepository membreRepository;

    private UUID notifId;
    private UUID membreId;
    private Membre membre;
    private NotificationResponse notifResponse;

    @BeforeEach
    void setup() {
        notifId = UUID.randomUUID();
        membreId = UUID.randomUUID();

        membre = new Membre();
        membre.setId(membreId);
        membre.setEmail("user@test.com");

        notifResponse = NotificationResponse.builder()
                .typeNotification("IN_APP")
                .statut("EN_ATTENTE")
                .sujet("Test notification")
                .membreId(membreId)
                .build();
    }

    // =========================================================================
    // GET /api/notifications/me
    // =========================================================================

    @Test
    @TestSecurity(user = "user@test.com", roles = {"ADMIN"})
    @DisplayName("GET /me - membre trouvé → 200 avec liste")
    void mesNotifications_membreTrouve_returns200() {
        when(membreRepository.findByEmail("user@test.com")).thenReturn(Optional.of(membre));
        when(notificationService.listerNotificationsParMembre(membreId))
                .thenReturn(List.of(notifResponse));

        given()
                .when()
                .get(BASE + "/me")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "unknown@test.com", roles = {"ADMIN"})
    @DisplayName("GET /me - membre non trouvé → 200 avec liste vide")
    void mesNotifications_membreIntrouvable_returns200EmptyList() {
        when(membreRepository.findByEmail("unknown@test.com")).thenReturn(Optional.empty());

        given()
                .when()
                .get(BASE + "/me")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "error@test.com", roles = {"ADMIN"})
    @DisplayName("GET /me - exception service → 400")
    void mesNotifications_serviceException_returns400() {
        when(membreRepository.findByEmail("error@test.com")).thenReturn(Optional.of(membre));
        when(notificationService.listerNotificationsParMembre(any()))
                .thenThrow(new RuntimeException("Service error"));

        given()
                .when()
                .get(BASE + "/me")
                .then()
                .statusCode(400);
    }

    // =========================================================================
    // GET /api/notifications/me/non-lues
    // =========================================================================

    @Test
    @TestSecurity(user = "user@test.com", roles = {"MEMBRE"})
    @DisplayName("GET /me/non-lues - membre trouvé → 200")
    void mesNotificationsNonLues_membreTrouve_returns200() {
        when(membreRepository.findByEmail("user@test.com")).thenReturn(Optional.of(membre));
        when(notificationService.listerNotificationsNonLuesParMembre(membreId))
                .thenReturn(Collections.emptyList());

        given()
                .when()
                .get(BASE + "/me/non-lues")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "ghost@test.com", roles = {"MEMBRE"})
    @DisplayName("GET /me/non-lues - membre non trouvé → 200 liste vide")
    void mesNotificationsNonLues_membreIntrouvable_returns200() {
        when(membreRepository.findByEmail("ghost@test.com")).thenReturn(Optional.empty());

        given()
                .when()
                .get(BASE + "/me/non-lues")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "crash@test.com", roles = {"MEMBRE"})
    @DisplayName("GET /me/non-lues - exception → 400")
    void mesNotificationsNonLues_exception_returns400() {
        when(membreRepository.findByEmail("crash@test.com")).thenReturn(Optional.of(membre));
        when(notificationService.listerNotificationsNonLuesParMembre(any()))
                .thenThrow(new RuntimeException("Boom"));

        given()
                .when()
                .get(BASE + "/me/non-lues")
                .then()
                .statusCode(400);
    }

    // =========================================================================
    // POST /api/notifications/templates
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /templates - succès → 201")
    void creerTemplate_success_returns201() {
        TemplateNotificationResponse templateResponse = TemplateNotificationResponse.builder()
                .code("TPL_WELCOME")
                .sujet("Bienvenue")
                .build();

        when(notificationService.creerTemplate(any())).thenReturn(templateResponse);

        String body = """
                {
                  "code": "TPL_WELCOME",
                  "sujet": "Bienvenue",
                  "corpsTexte": "Bonjour"
                }
                """;

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post(BASE + "/templates")
                .then()
                .statusCode(201);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /templates - code existant → 400")
    void creerTemplate_codeExistant_returns400() {
        when(notificationService.creerTemplate(any()))
                .thenThrow(new IllegalArgumentException("Un template avec ce code existe déjà"));

        String body = """
                {
                  "code": "TPL_EXISTING",
                  "sujet": "Doublon"
                }
                """;

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post(BASE + "/templates")
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /templates - exception générique → 400")
    void creerTemplate_genericException_returns400() {
        when(notificationService.creerTemplate(any()))
                .thenThrow(new RuntimeException("DB connection lost"));

        String body = """
                {
                  "code": "TPL_CRASH",
                  "sujet": "Crash"
                }
                """;

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post(BASE + "/templates")
                .then()
                .statusCode(400);
    }

    // =========================================================================
    // POST /api/notifications
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/notifications - succès → 201")
    void creerNotification_success_returns201() {
        when(notificationService.creerNotification(any())).thenReturn(notifResponse);

        String body = String.format("""
                {
                  "typeNotification": "IN_APP",
                  "sujet": "Test",
                  "membreId": "%s"
                }
                """, membreId);

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post(BASE)
                .then()
                .statusCode(201);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/notifications - exception service → 400")
    void creerNotification_exception_returns400() {
        when(notificationService.creerNotification(any()))
                .thenThrow(new RuntimeException("Erreur de création"));

        String body = """
                {
                  "typeNotification": "EMAIL",
                  "sujet": "Crash"
                }
                """;

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post(BASE)
                .then()
                .statusCode(400);
    }

    // =========================================================================
    // POST /api/notifications/{id}/marquer-lue
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /{id}/marquer-lue - succès → 200")
    void marquerCommeLue_success_returns200() {
        when(notificationService.marquerCommeLue(notifId)).thenReturn(notifResponse);

        given()
                .contentType(ContentType.JSON)
                .pathParam("id", notifId)
                .when()
                .post(BASE + "/{id}/marquer-lue")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /{id}/marquer-lue - notification inexistante → 404")
    void marquerCommeLue_notFound_returns404() {
        UUID unknownId = UUID.randomUUID();
        when(notificationService.marquerCommeLue(unknownId))
                .thenThrow(new NotFoundException("Notification non trouvée"));

        given()
                .contentType(ContentType.JSON)
                .pathParam("id", unknownId)
                .when()
                .post(BASE + "/{id}/marquer-lue")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /{id}/marquer-lue - exception générique → 400")
    void marquerCommeLue_genericException_returns400() {
        when(notificationService.marquerCommeLue(any()))
                .thenThrow(new RuntimeException("Transaction error"));

        given()
                .contentType(ContentType.JSON)
                .pathParam("id", UUID.randomUUID())
                .when()
                .post(BASE + "/{id}/marquer-lue")
                .then()
                .statusCode(400);
    }

    // =========================================================================
    // GET /api/notifications/{id}
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /{id} - notification trouvée → 200")
    void trouverNotificationParId_found_returns200() {
        when(notificationService.trouverNotificationParId(notifId)).thenReturn(notifResponse);

        given()
                .pathParam("id", notifId)
                .when()
                .get(BASE + "/{id}")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /{id} - notification inexistante → 404")
    void trouverNotificationParId_notFound_returns404() {
        UUID unknownId = UUID.randomUUID();
        when(notificationService.trouverNotificationParId(unknownId))
                .thenThrow(new NotFoundException("Notification non trouvée"));

        given()
                .pathParam("id", unknownId)
                .when()
                .get(BASE + "/{id}")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /{id} - exception générique → 400")
    void trouverNotificationParId_genericException_returns400() {
        when(notificationService.trouverNotificationParId(any()))
                .thenThrow(new RuntimeException("Unexpected error"));

        given()
                .pathParam("id", UUID.randomUUID())
                .when()
                .get(BASE + "/{id}")
                .then()
                .statusCode(400);
    }

    // =========================================================================
    // GET /api/notifications/membre/{membreId}
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /membre/{membreId} - succès → 200")
    void listerNotificationsParMembre_returns200() {
        when(notificationService.listerNotificationsParMembre(membreId))
                .thenReturn(List.of(notifResponse));

        given()
                .pathParam("membreId", membreId)
                .when()
                .get(BASE + "/membre/{membreId}")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /membre/{membreId} - liste vide → 200")
    void listerNotificationsParMembre_empty_returns200() {
        when(notificationService.listerNotificationsParMembre(membreId))
                .thenReturn(Collections.emptyList());

        given()
                .pathParam("membreId", membreId)
                .when()
                .get(BASE + "/membre/{membreId}")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /membre/{membreId} - exception → 400")
    void listerNotificationsParMembre_exception_returns400() {
        when(notificationService.listerNotificationsParMembre(any()))
                .thenThrow(new RuntimeException("DB error"));

        given()
                .pathParam("membreId", UUID.randomUUID())
                .when()
                .get(BASE + "/membre/{membreId}")
                .then()
                .statusCode(400);
    }

    // =========================================================================
    // GET /api/notifications/membre/{membreId}/non-lues
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /membre/{membreId}/non-lues - succès → 200")
    void listerNotificationsNonLuesParMembre_returns200() {
        when(notificationService.listerNotificationsNonLuesParMembre(membreId))
                .thenReturn(Collections.emptyList());

        given()
                .pathParam("membreId", membreId)
                .when()
                .get(BASE + "/membre/{membreId}/non-lues")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /membre/{membreId}/non-lues - exception → 400")
    void listerNotificationsNonLuesParMembre_exception_returns400() {
        when(notificationService.listerNotificationsNonLuesParMembre(any()))
                .thenThrow(new RuntimeException("Timeout"));

        given()
                .pathParam("membreId", UUID.randomUUID())
                .when()
                .get(BASE + "/membre/{membreId}/non-lues")
                .then()
                .statusCode(400);
    }

    // =========================================================================
    // GET /api/notifications/en-attente-envoi
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /en-attente-envoi - succès → 200")
    void listerNotificationsEnAttenteEnvoi_returns200() {
        when(notificationService.listerNotificationsEnAttenteEnvoi())
                .thenReturn(List.of(notifResponse));

        given()
                .when()
                .get(BASE + "/en-attente-envoi")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /en-attente-envoi - exception → 400")
    void listerNotificationsEnAttenteEnvoi_exception_returns400() {
        when(notificationService.listerNotificationsEnAttenteEnvoi())
                .thenThrow(new RuntimeException("Query error"));

        given()
                .when()
                .get(BASE + "/en-attente-envoi")
                .then()
                .statusCode(400);
    }

    // =========================================================================
    // POST /api/notifications/groupees
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /groupees - succès → 200")
    void envoyerNotificationsGroupees_success_returns200() {
        when(notificationService.envoyerNotificationsGroupees(any(), any(), any(), any()))
                .thenReturn(3);

        String body = String.format("""
                {
                  "membreIds": ["%s", "%s"],
                  "sujet": "Réunion",
                  "corps": "Présence requise",
                  "canaux": ["IN_APP", "EMAIL"]
                }
                """, UUID.randomUUID(), UUID.randomUUID());

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post(BASE + "/groupees")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /groupees - liste membres vide → 400 (IllegalArgumentException)")
    void envoyerNotificationsGroupees_listeVide_returns400() {
        when(notificationService.envoyerNotificationsGroupees(any(), any(), any(), any()))
                .thenThrow(new IllegalArgumentException("La liste des membres ne peut pas être vide"));

        String body = """
                {
                  "membreIds": [],
                  "sujet": "Test",
                  "corps": "Vide"
                }
                """;

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post(BASE + "/groupees")
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /groupees - exception générique → 400")
    void envoyerNotificationsGroupees_genericException_returns400() {
        when(notificationService.envoyerNotificationsGroupees(any(), any(), any(), any()))
                .thenThrow(new RuntimeException("Internal error"));

        String body = String.format("""
                {
                  "membreIds": ["%s"],
                  "sujet": "Test"
                }
                """, UUID.randomUUID());

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post(BASE + "/groupees")
                .then()
                .statusCode(400);
    }
}
