package dev.lions.unionflow.server.resource;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;

import dev.lions.unionflow.server.api.dto.communication.request.CreateConversationRequest;
import dev.lions.unionflow.server.api.dto.communication.response.ConversationResponse;
import dev.lions.unionflow.server.api.enums.communication.ConversationType;
import dev.lions.unionflow.server.service.ConversationService;
import dev.lions.unionflow.server.service.support.SecuriteHelper;
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.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
 * Tests d'intégration REST pour ConversationResource.
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2026-03-21
 */
@QuarkusTest
class ConversationResourceTest {

    private static final String BASE_PATH = "/api/conversations";
    private static final String MEMBRE_ID = "00000000-0000-0000-0000-000000000010";
    private static final String CONVERSATION_ID = "00000000-0000-0000-0000-000000000011";
    private static final String ORG_ID = "00000000-0000-0000-0000-000000000012";

    @InjectMock
    ConversationService conversationService;

    @InjectMock
    SecuriteHelper securiteHelper;

    @BeforeEach
    void setup() {
        when(securiteHelper.resolveMembreId()).thenReturn(UUID.fromString(MEMBRE_ID));
    }

    private ConversationResponse buildConversationResponse() {
        return ConversationResponse.builder()
                .id(UUID.fromString(CONVERSATION_ID))
                .name("Discussion générale")
                .description("Conversation test")
                .type(ConversationType.GROUP)
                .participantIds(List.of(UUID.fromString(MEMBRE_ID)))
                .unreadCount(0)
                .isMuted(false)
                .isPinned(false)
                .isArchived(false)
                .createdAt(LocalDateTime.now())
                .updatedAt(LocalDateTime.now())
                .build();
    }

    // -------------------------------------------------------------------------
    // GET /api/conversations
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "alice@test.com", roles = {"MEMBRE"})
    @DisplayName("getConversations retourne 200 avec liste vide")
    void getConversations_returnsEmptyList_200() {
        when(conversationService.getConversations(any(), any(), anyBoolean()))
                .thenReturn(Collections.emptyList());

        given()
                .when()
                .get(BASE_PATH)
                .then()
                .statusCode(200)
                .contentType(ContentType.JSON)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "alice@test.com", roles = {"MEMBRE"})
    @DisplayName("getConversations avec includeArchived=true retourne 200")
    void getConversations_withIncludeArchived_returns200() {
        when(conversationService.getConversations(any(), any(), eq(true)))
                .thenReturn(List.of(buildConversationResponse()));

        given()
                .queryParam("includeArchived", true)
                .when()
                .get(BASE_PATH)
                .then()
                .statusCode(200)
                .contentType(ContentType.JSON);
    }

    @Test
    @TestSecurity(user = "alice@test.com", roles = {"MEMBRE"})
    @DisplayName("getConversations avec organisationId filtre par organisation")
    void getConversations_withOrganisationId_returns200() {
        when(conversationService.getConversations(any(), eq(UUID.fromString(ORG_ID)), anyBoolean()))
                .thenReturn(List.of(buildConversationResponse()));

        given()
                .queryParam("organisationId", ORG_ID)
                .when()
                .get(BASE_PATH)
                .then()
                .statusCode(200)
                .contentType(ContentType.JSON);
    }

    @Test
    @TestSecurity(user = "alice@test.com", roles = {"MEMBRE"})
    @DisplayName("getConversations retourne 200 avec liste non vide")
    void getConversations_withResults_returns200() {
        when(conversationService.getConversations(any(), any(), anyBoolean()))
                .thenReturn(List.of(buildConversationResponse()));

        given()
                .when()
                .get(BASE_PATH)
                .then()
                .statusCode(200)
                .contentType(ContentType.JSON);
    }

    // -------------------------------------------------------------------------
    // GET /api/conversations/{id}
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "alice@test.com", roles = {"MEMBRE"})
    @DisplayName("getConversationById retourne 200 quand conversation trouvée")
    void getConversationById_found_returns200() {
        when(conversationService.getConversationById(eq(UUID.fromString(CONVERSATION_ID)), any()))
                .thenReturn(buildConversationResponse());

        given()
                .when()
                .get(BASE_PATH + "/{id}", CONVERSATION_ID)
                .then()
                .statusCode(200)
                .contentType(ContentType.JSON)
                .body("id", notNullValue());
    }

    @Test
    @TestSecurity(user = "alice@test.com", roles = {"MEMBRE"})
    @DisplayName("getConversationById retourne 404 quand conversation non trouvée")
    void getConversationById_notFound_returns404() {
        when(conversationService.getConversationById(any(), any()))
                .thenThrow(new NotFoundException("Conversation non trouvée ou accès refusé"));

        given()
                .when()
                .get(BASE_PATH + "/{id}", UUID.randomUUID().toString())
                .then()
                .statusCode(404);
    }

    // -------------------------------------------------------------------------
    // POST /api/conversations
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "alice@test.com", roles = {"MEMBRE"})
    @DisplayName("createConversation retourne 201 avec body valide")
    void createConversation_validRequest_returns201() {
        when(conversationService.createConversation(any(CreateConversationRequest.class), any()))
                .thenReturn(buildConversationResponse());

        String body = """
                {
                    "name": "Nouveau groupe",
                    "description": "Description du groupe",
                    "type": "GROUP",
                    "participantIds": ["%s"]
                }
                """.formatted(MEMBRE_ID);

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

    @Test
    @TestSecurity(user = "alice@test.com", roles = {"MEMBRE"})
    @DisplayName("createConversation avec body invalide (sans name) retourne 400")
    void createConversation_invalidRequest_returns400() {
        String body = """
                {
                    "description": "Sans nom",
                    "type": "GROUP",
                    "participantIds": ["%s"]
                }
                """.formatted(MEMBRE_ID);

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

    // -------------------------------------------------------------------------
    // PUT /api/conversations/{id}/archive
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "alice@test.com", roles = {"MEMBRE"})
    @DisplayName("archiveConversation retourne 204 quand succès")
    void archiveConversation_success_returns204() {
        doNothing().when(conversationService).archiveConversation(any(), any(), anyBoolean());

        given()
                .when()
                .put(BASE_PATH + "/{id}/archive", CONVERSATION_ID)
                .then()
                .statusCode(204);
    }

    @Test
    @TestSecurity(user = "alice@test.com", roles = {"MEMBRE"})
    @DisplayName("archiveConversation avec archive=false retourne 204")
    void archiveConversation_unarchive_returns204() {
        doNothing().when(conversationService).archiveConversation(any(), any(), eq(false));

        given()
                .queryParam("archive", false)
                .when()
                .put(BASE_PATH + "/{id}/archive", CONVERSATION_ID)
                .then()
                .statusCode(204);
    }

    @Test
    @TestSecurity(user = "alice@test.com", roles = {"MEMBRE"})
    @DisplayName("archiveConversation non trouvée retourne 404")
    void archiveConversation_notFound_returns404() {
        doThrow(new NotFoundException("Conversation non trouvée"))
                .when(conversationService).archiveConversation(any(), any(), anyBoolean());

        given()
                .when()
                .put(BASE_PATH + "/{id}/archive", UUID.randomUUID().toString())
                .then()
                .statusCode(404);
    }

    // -------------------------------------------------------------------------
    // PUT /api/conversations/{id}/mark-read
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "alice@test.com", roles = {"MEMBRE"})
    @DisplayName("markAsRead retourne 204 quand succès")
    void markAsRead_success_returns204() {
        doNothing().when(conversationService).markAsRead(any(), any());

        given()
                .when()
                .put(BASE_PATH + "/{id}/mark-read", CONVERSATION_ID)
                .then()
                .statusCode(204);
    }

    @Test
    @TestSecurity(user = "alice@test.com", roles = {"MEMBRE"})
    @DisplayName("markAsRead conversation non trouvée retourne 404")
    void markAsRead_notFound_returns404() {
        doThrow(new NotFoundException("Conversation non trouvée"))
                .when(conversationService).markAsRead(any(), any());

        given()
                .when()
                .put(BASE_PATH + "/{id}/mark-read", UUID.randomUUID().toString())
                .then()
                .statusCode(404);
    }

    // -------------------------------------------------------------------------
    // PUT /api/conversations/{id}/toggle-mute
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "alice@test.com", roles = {"MEMBRE"})
    @DisplayName("toggleMute retourne 204 quand succès")
    void toggleMute_success_returns204() {
        doNothing().when(conversationService).toggleMute(any(), any());

        given()
                .when()
                .put(BASE_PATH + "/{id}/toggle-mute", CONVERSATION_ID)
                .then()
                .statusCode(204);
    }

    @Test
    @TestSecurity(user = "alice@test.com", roles = {"MEMBRE"})
    @DisplayName("toggleMute conversation non trouvée retourne 404")
    void toggleMute_notFound_returns404() {
        doThrow(new NotFoundException("Conversation non trouvée"))
                .when(conversationService).toggleMute(any(), any());

        given()
                .when()
                .put(BASE_PATH + "/{id}/toggle-mute", UUID.randomUUID().toString())
                .then()
                .statusCode(404);
    }

    // -------------------------------------------------------------------------
    // PUT /api/conversations/{id}/toggle-pin
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "alice@test.com", roles = {"MEMBRE"})
    @DisplayName("togglePin retourne 204 quand succès")
    void togglePin_success_returns204() {
        doNothing().when(conversationService).togglePin(any(), any());

        given()
                .when()
                .put(BASE_PATH + "/{id}/toggle-pin", CONVERSATION_ID)
                .then()
                .statusCode(204);
    }

    @Test
    @TestSecurity(user = "alice@test.com", roles = {"MEMBRE"})
    @DisplayName("togglePin conversation non trouvée retourne 404")
    void togglePin_notFound_returns404() {
        doThrow(new NotFoundException("Conversation non trouvée"))
                .when(conversationService).togglePin(any(), any());

        given()
                .when()
                .put(BASE_PATH + "/{id}/toggle-pin", UUID.randomUUID().toString())
                .then()
                .statusCode(404);
    }

    // -------------------------------------------------------------------------
    // Sécurité — non authentifié
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("getConversations sans authentification retourne 401")
    void getConversations_unauthenticated_returns401() {
        given()
                .when()
                .get(BASE_PATH)
                .then()
                .statusCode(401);
    }
}
