package dev.lions.unionflow.server.resource;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;

import dev.lions.unionflow.server.api.dto.messagerie.response.ContactPolicyResponse;
import dev.lions.unionflow.server.api.dto.messagerie.response.ConversationResponse;
import dev.lions.unionflow.server.api.dto.messagerie.response.ConversationSummaryResponse;
import dev.lions.unionflow.server.api.dto.messagerie.response.MessageResponse;
import dev.lions.unionflow.server.service.MessagingService;
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 java.util.Collections;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;

/**
 * Tests d'intégration REST pour {@link MessagingResource}.
 *
 * @author UnionFlow Team
 * @version 4.0
 * @since 2026-04-13
 */
@QuarkusTest
@DisplayName("MessagingResource")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class MessagingResourceTest {

    private static final String BASE       = "/api/messagerie";
    private static final String CONV_ID    = "00000000-0000-0000-0000-000000000001";
    private static final String MSG_ID     = "00000000-0000-0000-0000-000000000002";
    private static final String MEMBRE_ID  = "00000000-0000-0000-0000-000000000003";
    private static final String ORG_ID     = "00000000-0000-0000-0000-000000000004";

    @InjectMock
    MessagingService messagingService;

    // ── POST /conversations/directe ───────────────────────────────────────────

    @Test
    @Order(1)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("demarrerConversationDirecte — requête valide → 201")
    void demarrerConversationDirecte_valid_returns201() {
        ConversationResponse conv = ConversationResponse.builder()
            .id(UUID.fromString(CONV_ID))
            .typeConversation("DIRECTE")
            .statut("ACTIVE")
            .build();
        when(messagingService.demarrerConversationDirecte(any())).thenReturn(conv);

        given()
            .contentType(ContentType.JSON)
            .body("{\"destinataireId\":\"" + MEMBRE_ID + "\",\"organisationId\":\"" + ORG_ID + "\"}")
            .when().post(BASE + "/conversations/directe")
            .then().statusCode(201)
            .body("typeConversation", equalTo("DIRECTE"))
            .body("statut", equalTo("ACTIVE"));
    }

    @Test
    @Order(2)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("demarrerConversationDirecte — destinataireId manquant → 400")
    void demarrerConversationDirecte_missingDestinataire_returns400() {
        given()
            .contentType(ContentType.JSON)
            .body("{\"organisationId\":\"" + ORG_ID + "\"}")
            .when().post(BASE + "/conversations/directe")
            .then().statusCode(400);
    }

    // ── POST /conversations/role ──────────────────────────────────────────────

    @Test
    @Order(3)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("demarrerConversationRole — requête valide → 201")
    void demarrerConversationRole_valid_returns201() {
        ConversationResponse conv = ConversationResponse.builder()
            .id(UUID.fromString(CONV_ID))
            .typeConversation("ROLE_CANAL")
            .roleCible("TRESORIER")
            .build();
        when(messagingService.demarrerConversationRole(any())).thenReturn(conv);

        given()
            .contentType(ContentType.JSON)
            .body("{\"organisationId\":\"" + ORG_ID + "\","
                + "\"roleCible\":\"TRESORIER\","
                + "\"contenuInitial\":\"Bonjour\"}")
            .when().post(BASE + "/conversations/role")
            .then().statusCode(201)
            .body("typeConversation", equalTo("ROLE_CANAL"))
            .body("roleCible", equalTo("TRESORIER"));
    }

    @Test
    @Order(4)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("demarrerConversationRole — rôle invalide → 400")
    void demarrerConversationRole_invalidRole_returns400() {
        given()
            .contentType(ContentType.JSON)
            .body("{\"organisationId\":\"" + ORG_ID + "\","
                + "\"roleCible\":\"DIRECTEUR\","
                + "\"contenuInitial\":\"Bonjour\"}")
            .when().post(BASE + "/conversations/role")
            .then().statusCode(400);
    }

    // ── GET /conversations ────────────────────────────────────────────────────

    @Test
    @Order(5)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("getMesConversations — liste vide → 200")
    void getMesConversations_empty_returns200() {
        when(messagingService.getMesConversations()).thenReturn(Collections.emptyList());

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

    @Test
    @Order(6)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("getMesConversations — liste non vide → 200")
    void getMesConversations_nonEmpty_returns200() {
        ConversationSummaryResponse summary = ConversationSummaryResponse.builder()
            .id(UUID.fromString(CONV_ID))
            .typeConversation("DIRECTE")
            .titre("Alice Dupont")
            .build();
        when(messagingService.getMesConversations()).thenReturn(List.of(summary));

        given()
            .when().get(BASE + "/conversations")
            .then().statusCode(200)
            .body("[0].titre", equalTo("Alice Dupont"));
    }

    // ── GET /conversations/{id} ───────────────────────────────────────────────

    @Test
    @Order(7)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("getConversation — trouvée → 200")
    void getConversation_found_returns200() {
        ConversationResponse conv = ConversationResponse.builder()
            .id(UUID.fromString(CONV_ID))
            .typeConversation("DIRECTE")
            .titre("Bob Martin")
            .build();
        when(messagingService.getConversation(any(UUID.class))).thenReturn(conv);

        given()
            .when().get(BASE + "/conversations/" + CONV_ID)
            .then().statusCode(200)
            .body("titre", equalTo("Bob Martin"));
    }

    @Test
    @Order(8)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("getConversation — non trouvée → 404")
    void getConversation_notFound_returns404() {
        when(messagingService.getConversation(any(UUID.class)))
            .thenThrow(new NotFoundException("conversation non trouvée"));

        given()
            .when().get(BASE + "/conversations/" + CONV_ID)
            .then().statusCode(404);
    }

    // ── DELETE /conversations/{id} ────────────────────────────────────────────

    @Test
    @Order(9)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("archiverConversation → 200")
    void archiverConversation_returns200() {
        ConversationResponse conv = ConversationResponse.builder()
            .id(UUID.fromString(CONV_ID))
            .statut("ARCHIVEE")
            .build();
        when(messagingService.archiverConversation(any(UUID.class))).thenReturn(conv);

        given()
            .when().delete(BASE + "/conversations/" + CONV_ID)
            .then().statusCode(200)
            .body("statut", equalTo("ARCHIVEE"));
    }

    // ── POST /conversations/{id}/messages ─────────────────────────────────────

    @Test
    @Order(10)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("envoyerMessage — message texte → 201")
    void envoyerMessage_texte_returns201() {
        MessageResponse msg = MessageResponse.builder()
            .id(UUID.fromString(MSG_ID))
            .typeMessage("TEXTE")
            .contenu("Bonjour !")
            .build();
        when(messagingService.envoyerMessage(any(UUID.class), any())).thenReturn(msg);

        given()
            .contentType(ContentType.JSON)
            .body("{\"typeMessage\":\"TEXTE\",\"contenu\":\"Bonjour !\"}")
            .when().post(BASE + "/conversations/" + CONV_ID + "/messages")
            .then().statusCode(201)
            .body("typeMessage", equalTo("TEXTE"))
            .body("contenu", equalTo("Bonjour !"));
    }

    @Test
    @Order(11)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("envoyerMessage — type invalide → 400")
    void envoyerMessage_invalidType_returns400() {
        given()
            .contentType(ContentType.JSON)
            .body("{\"typeMessage\":\"VIDEO\",\"contenu\":\"test\"}")
            .when().post(BASE + "/conversations/" + CONV_ID + "/messages")
            .then().statusCode(400);
    }

    // ── GET /conversations/{id}/messages ──────────────────────────────────────

    @Test
    @Order(12)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("getMessages — page 0 → 200")
    void getMessages_page0_returns200() {
        when(messagingService.getMessages(any(UUID.class), anyInt()))
            .thenReturn(Collections.emptyList());

        given()
            .when().get(BASE + "/conversations/" + CONV_ID + "/messages")
            .then().statusCode(200);
    }

    @Test
    @Order(13)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("getMessages — page custom → 200")
    void getMessages_customPage_returns200() {
        when(messagingService.getMessages(any(UUID.class), anyInt()))
            .thenReturn(Collections.emptyList());

        given()
            .queryParam("page", 2)
            .when().get(BASE + "/conversations/" + CONV_ID + "/messages")
            .then().statusCode(200);
    }

    // ── PUT /conversations/{id}/lire ──────────────────────────────────────────

    @Test
    @Order(14)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("marquerLu → 204")
    void marquerLu_returns204() {
        doNothing().when(messagingService).marquerConversationLue(any(UUID.class));

        given()
            .when().put(BASE + "/conversations/" + CONV_ID + "/lire")
            .then().statusCode(204);
    }

    // ── DELETE /conversations/{cId}/messages/{mId} ────────────────────────────

    @Test
    @Order(15)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("supprimerMessage → 204")
    void supprimerMessage_returns204() {
        doNothing().when(messagingService).supprimerMessage(any(UUID.class), any(UUID.class));

        given()
            .when().delete(BASE + "/conversations/" + CONV_ID + "/messages/" + MSG_ID)
            .then().statusCode(204);
    }

    @Test
    @Order(16)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("supprimerMessage — non trouvé → 404")
    void supprimerMessage_notFound_returns404() {
        org.mockito.Mockito.doThrow(new NotFoundException("message introuvable"))
            .when(messagingService).supprimerMessage(any(UUID.class), any(UUID.class));

        given()
            .when().delete(BASE + "/conversations/" + CONV_ID + "/messages/" + MSG_ID)
            .then().statusCode(404);
    }

    // ── POST /blocages ────────────────────────────────────────────────────────

    @Test
    @Order(17)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("bloquerMembre — requête valide → 204")
    void bloquerMembre_valid_returns204() {
        doNothing().when(messagingService).bloquerMembre(any());

        given()
            .contentType(ContentType.JSON)
            .body("{\"membreABloquerId\":\"" + MEMBRE_ID + "\","
                + "\"organisationId\":\"" + ORG_ID + "\"}")
            .when().post(BASE + "/blocages")
            .then().statusCode(204);
    }

    @Test
    @Order(18)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("bloquerMembre — membreABloquerId manquant → 400")
    void bloquerMembre_missingId_returns400() {
        given()
            .contentType(ContentType.JSON)
            .body("{\"organisationId\":\"" + ORG_ID + "\"}")
            .when().post(BASE + "/blocages")
            .then().statusCode(400);
    }

    // ── DELETE /blocages/{membreId} ───────────────────────────────────────────

    @Test
    @Order(19)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("debloquerMembre → 204")
    void debloquerMembre_returns204() {
        doNothing().when(messagingService).debloquerMembre(any(UUID.class), any());

        given()
            .queryParam("organisationId", ORG_ID)
            .when().delete(BASE + "/blocages/" + MEMBRE_ID)
            .then().statusCode(204);
    }

    // ── GET /blocages ─────────────────────────────────────────────────────────

    @Test
    @Order(20)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("getMesBlocages → 200")
    void getMesBlocages_returns200() {
        when(messagingService.getMesBlocages()).thenReturn(Collections.emptyList());

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

    // ── GET /politique/{organisationId} ───────────────────────────────────────

    @Test
    @Order(21)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("getPolitique → 200")
    void getPolitique_returns200() {
        ContactPolicyResponse policy = ContactPolicyResponse.builder()
            .id(UUID.randomUUID())
            .organisationId(UUID.fromString(ORG_ID))
            .typePolitique("OUVERT")
            .autoriserMembreVersMembre(true)
            .autoriserMembreVersRole(true)
            .autoriserNotesVocales(true)
            .build();
        when(messagingService.getPolitique(any(UUID.class))).thenReturn(policy);

        given()
            .when().get(BASE + "/politique/" + ORG_ID)
            .then().statusCode(200)
            .body("typePolitique", equalTo("OUVERT"))
            .body("autoriserNotesVocales", equalTo(true));
    }

    // ── PUT /politique/{organisationId} ───────────────────────────────────────

    @Test
    @Order(22)
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("mettreAJourPolitique — ADMIN → 200")
    void mettreAJourPolitique_admin_returns200() {
        ContactPolicyResponse updated = ContactPolicyResponse.builder()
            .typePolitique("BUREAU_SEULEMENT")
            .build();
        when(messagingService.mettreAJourPolitique(any(UUID.class), any())).thenReturn(updated);

        given()
            .contentType(ContentType.JSON)
            .body("{\"typePolitique\":\"BUREAU_SEULEMENT\"}")
            .when().put(BASE + "/politique/" + ORG_ID)
            .then().statusCode(200)
            .body("typePolitique", equalTo("BUREAU_SEULEMENT"));
    }

    @Test
    @Order(23)
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("mettreAJourPolitique — MEMBRE → 403")
    void mettreAJourPolitique_membre_returns403() {
        given()
            .contentType(ContentType.JSON)
            .body("{\"typePolitique\":\"BUREAU_SEULEMENT\"}")
            .when().put(BASE + "/politique/" + ORG_ID)
            .then().statusCode(403);
    }

    @Test
    @Order(24)
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("mettreAJourPolitique — politique invalide → 400")
    void mettreAJourPolitique_invalidPolicy_returns400() {
        given()
            .contentType(ContentType.JSON)
            .body("{\"typePolitique\":\"LIBRE\"}")
            .when().put(BASE + "/politique/" + ORG_ID)
            .then().statusCode(400);
    }
}
