package dev.lions.unionflow.server.resource;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

import dev.lions.unionflow.server.service.OrganisationService;
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.Test;

/**
 * Tests d'intégration REST pour AdminAssocierOrganisationResource.
 *
 * <p>La resource délègue l'opération d'association à {@link OrganisationService}.
 * Le mock du service permet de tester tous les cas sans accès à la base de données.
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2026-03-20
 */
@QuarkusTest
class AdminAssocierOrganisationResourceTest {

    private static final String BASE_PATH = "/api/admin/associer-organisation";
    private static final String VALID_BODY = """
            {"email":"user@test.com","organisationId":"00000000-0000-0000-0000-000000000001"}
            """;

    @InjectMock
    OrganisationService organisationService;

    // -------------------------------------------------------------------------
    // POST /api/admin/associer-organisation
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "superadmin@test.com", roles = {"SUPER_ADMIN"})
    void associer_orgNotFound_returns404() {
        doThrow(new NotFoundException("Organisation non trouvée"))
                .when(organisationService)
                .associerUtilisateurAOrganisation(anyString(), any());

        given()
                .contentType(ContentType.JSON)
                .body(VALID_BODY)
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(404)
                .body("error", notNullValue());
    }

    @Test
    @TestSecurity(user = "superadmin@test.com", roles = {"SUPER_ADMIN"})
    void associer_membreExistant_success_returns200() {
        doNothing().when(organisationService)
                .associerUtilisateurAOrganisation(anyString(), any());

        given()
                .contentType(ContentType.JSON)
                .body(VALID_BODY)
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(200)
                .contentType(ContentType.JSON)
                .body("success", equalTo(true))
                .body("email", equalTo("user@test.com"));
    }

    @Test
    @TestSecurity(user = "superadmin@test.com", roles = {"SUPER_ADMIN"})
    void associer_membreInexistant_creeMembre_returns200() {
        // Le service gère la création du membre automatiquement — le resource retourne 200
        doNothing().when(organisationService)
                .associerUtilisateurAOrganisation(anyString(), any());

        String body = """
                {"email":"nouveau@test.com","organisationId":"00000000-0000-0000-0000-000000000001"}
                """;

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(200)
                .contentType(ContentType.JSON)
                .body("success", equalTo(true))
                .body("email", equalTo("nouveau@test.com"));
    }

    @Test
    @TestSecurity(user = "superadmin@test.com", roles = {"SUPER_ADMIN"})
    void associer_alreadyAssociated_returns200_idempotent() {
        // L'opération est idempotente : si déjà associé, le service ne plante pas
        doNothing().when(organisationService)
                .associerUtilisateurAOrganisation(anyString(), any());

        given()
                .contentType(ContentType.JSON)
                .body(VALID_BODY)
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(200)
                .contentType(ContentType.JSON)
                .body("success", equalTo(true));
    }

    @Test
    @TestSecurity(user = "superadmin@test.com", roles = {"SUPER_ADMIN"})
    void associer_emailManquant_returns400() {
        given()
                .contentType(ContentType.JSON)
                .body("{\"organisationId\":\"00000000-0000-0000-0000-000000000001\"}")
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(400)
                .body("error", notNullValue());
    }

    @Test
    @TestSecurity(user = "superadmin@test.com", roles = {"SUPER_ADMIN"})
    void associer_organisationIdManquant_returns400() {
        given()
                .contentType(ContentType.JSON)
                .body("{\"email\":\"user@test.com\"}")
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(400)
                .body("error", notNullValue());
    }

    @Test
    @TestSecurity(user = "superadmin@test.com", roles = {"SUPER_ADMIN"})
    void associer_illegalArgument_returns400() {
        doThrow(new IllegalArgumentException("Argument invalide"))
                .when(organisationService)
                .associerUtilisateurAOrganisation(anyString(), any());

        given()
                .contentType(ContentType.JSON)
                .body(VALID_BODY)
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(400)
                .body("error", notNullValue());
    }

    @Test
    @TestSecurity(user = "superadmin@test.com", roles = {"SUPER_ADMIN"})
    void associer_genericException_returns500() {
        doThrow(new RuntimeException("Erreur inattendue"))
                .when(organisationService)
                .associerUtilisateurAOrganisation(anyString(), any());

        given()
                .contentType(ContentType.JSON)
                .body(VALID_BODY)
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(500)
                .body("error", notNullValue());
    }

    // -------------------------------------------------------------------------
    // Missed branches: request == null, email.isBlank()
    // -------------------------------------------------------------------------

    @Test
    @TestSecurity(user = "superadmin@test.com", roles = {"SUPER_ADMIN"})
    void associer_requestNull_returns400() {
        // Sending an empty body → JAX-RS deserialises to null record fields but the
        // outer record itself won't be null; however an empty JSON object produces
        // email=null → same 400 path. We also test a truly empty body which forces
        // the "request == null" guard (no JSON → null body param).
        given()
                .contentType(ContentType.JSON)
                .body("{}")
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(400)
                .body("error", notNullValue());
    }

    @Test
    @TestSecurity(user = "superadmin@test.com", roles = {"SUPER_ADMIN"})
    void associer_emailBlank_returns400() {
        // email is present but blank → request.email().isBlank() == true → 400
        given()
                .contentType(ContentType.JSON)
                .body("{\"email\":\"   \",\"organisationId\":\"00000000-0000-0000-0000-000000000001\"}")
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(400)
                .body("error", notNullValue());
    }

    @Test
    @TestSecurity(user = "superadmin@test.com", roles = {"SUPER_ADMIN"})
    void associer_noBody_returns400() {
        // No body → JAX-RS passes null as the record param → request == null branch
        given()
                .contentType(ContentType.JSON)
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(anyOf(equalTo(400), equalTo(415)));
    }
}
