package dev.lions.unionflow.server.resource;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;

import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import io.restassured.http.ContentType;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import java.time.LocalDateTime;
import java.util.UUID;
import org.junit.jupiter.api.*;

/**
 * Tests d'intégration pour OrganisationResource
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-19
 */
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class OrganisationResourceTest {

  private static final String BASE_PATH = "/api/organisations";

  @Inject
  OrganisationRepository organisationRepository;

  private Organisation testOrganisation;

  @BeforeEach
  @Transactional
  void setupTestData() {
    // Créer une organisation de test
    testOrganisation = Organisation.builder()
        .nom("Organisation Test")
        .typeOrganisation("ASSOCIATION")
        .statut("ACTIVE")
        .email("test-org-" + System.currentTimeMillis() + "@test.com")
        .telephone("+221701234567")
        .build();
    testOrganisation.setDateCreation(LocalDateTime.now());
    testOrganisation.setActif(true);
    organisationRepository.persist(testOrganisation);
  }

  @AfterEach
  @Transactional
  void cleanupTestData() {
    if (testOrganisation != null && testOrganisation.getId() != null) {
      Organisation orgToDelete = organisationRepository.findById(testOrganisation.getId());
      if (orgToDelete != null) {
        organisationRepository.delete(orgToDelete);
      }
    }
  }

  @Test
  @Order(1)
  @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
  @DisplayName("GET /api/organisations doit retourner la liste des organisations")
  void testListerOrganisations() {
    given()
        .when()
        .get(BASE_PATH)
        .then()
        .statusCode(200)
        .contentType(ContentType.JSON)
        .body("$", notNullValue());
  }

  @Test
  @Order(2)
  @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
  @DisplayName("GET /api/organisations/{id} doit retourner une organisation existante")
  void testObtenirOrganisation() {
    UUID orgId = testOrganisation.getId();

    given()
        .pathParam("id", orgId)
        .when()
        .get(BASE_PATH + "/{id}")
        .then()
        .statusCode(200)
        .contentType(ContentType.JSON)
        .body("id", equalTo(orgId.toString()))
        .body("nom", equalTo("Organisation Test"));
  }

  @Test
  @Order(3)
  @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
  @DisplayName("GET /api/organisations/{id} doit retourner 404 pour un ID inexistant")
  void testObtenirOrganisationInexistante() {
    UUID fakeId = UUID.randomUUID();

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

  @Test
  @Order(4)
  @TestSecurity(user = "superadmin@unionflow.com", roles = { "SUPER_ADMIN" })
  @DisplayName("POST /api/organisations doit créer une nouvelle organisation (SUPER_ADMIN)")
  void testCreerOrganisation() {
    java.util.Map<String, Object> newOrg = new java.util.HashMap<>();
    newOrg.put("nom", "Nouvelle Organisation Test");
    newOrg.put("typeOrganisation", "ASSOCIATION");
    newOrg.put("statut", "ACTIVE");
    newOrg.put("email", "nouvelle-org-" + System.currentTimeMillis() + "@test.com");
    newOrg.put("telephone", "+221701234568");

    String location = given()
        .contentType(ContentType.JSON)
        .body(newOrg)
        .when()
        .post(BASE_PATH)
        .then()
        .statusCode(201)
        .contentType(ContentType.JSON)
        .body("nom", equalTo("Nouvelle Organisation Test"))
        .body("id", notNullValue())
        .extract()
        .header("Location");

    // Nettoyer l'organisation créée
    if (location != null && location.contains("/")) {
      String idStr = location.substring(location.lastIndexOf("/") + 1);
      try {
        UUID createdId = UUID.fromString(idStr);
        Organisation created = organisationRepository.findById(createdId);
        if (created != null) {
          organisationRepository.delete(created);
        }
      } catch (Exception e) {
        // Ignorer les erreurs de nettoyage
      }
    }
  }

  @Test
  @Order(5)
  @TestSecurity(user = "superadmin@unionflow.com", roles = { "SUPER_ADMIN" })
  @DisplayName("POST /api/organisations doit retourner 400 pour données invalides (SUPER_ADMIN)")
  void testCreerOrganisationInvalide() {
    java.util.Map<String, Object> invalidOrg = new java.util.HashMap<>();
    invalidOrg.put("nom", ""); // Nom vide - invalide
    invalidOrg.put("typeOrganisation", "ASSOCIATION");
    invalidOrg.put("statut", "ACTIVE");
    invalidOrg.put("email", "invalid-email"); // Email invalide

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

  @Test
  @Order(6)
  @TestSecurity(user = "superadmin@unionflow.com", roles = { "SUPER_ADMIN" })
  @DisplayName("POST /api/organisations doit retourner 409 pour email dupliqué (SUPER_ADMIN)")
  void testCreerOrganisationEmailDuplique() {
    java.util.Map<String, Object> duplicateOrg = new java.util.HashMap<>();
    duplicateOrg.put("nom", "Autre Organisation");
    duplicateOrg.put("typeOrganisation", "ASSOCIATION");
    duplicateOrg.put("statut", "ACTIVE");
    duplicateOrg.put("email", testOrganisation.getEmail()); // Email déjà utilisé

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

  @Test
  @Order(7)
  @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
  @DisplayName("PUT /api/organisations/{id} doit mettre à jour une organisation")
  void testModifierOrganisation() {
    UUID orgId = testOrganisation.getId();
    java.util.Map<String, Object> updatedOrg = new java.util.HashMap<>();
    updatedOrg.put("nom", "Organisation Modifiée");
    updatedOrg.put("typeOrganisation", "ASSOCIATION");
    updatedOrg.put("statut", "ACTIVE");
    updatedOrg.put("email", testOrganisation.getEmail());
    updatedOrg.put("telephone", "+221701234999");

    given()
        .contentType(ContentType.JSON)
        .pathParam("id", orgId)
        .body(updatedOrg)
        .when()
        .put(BASE_PATH + "/{id}")
        .then()
        .statusCode(200)
        .contentType(ContentType.JSON)
        .body("nom", equalTo("Organisation Modifiée"))
        .body("telephone", equalTo("+221701234999"));
  }

  @Test
  @Order(8)
  @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
  @DisplayName("PUT /api/organisations/{id} doit retourner 404 pour ID inexistant")
  void testModifierOrganisationInexistante() {
    UUID fakeId = UUID.randomUUID();
    java.util.Map<String, Object> updatedOrg = new java.util.HashMap<>();
    updatedOrg.put("nom", "Organisation Test");
    updatedOrg.put("typeOrganisation", "ASSOCIATION");
    updatedOrg.put("statut", "ACTIVE");
    updatedOrg.put("email", "fake-" + System.currentTimeMillis() + "@test.com");

    given()
        .contentType(ContentType.JSON)
        .pathParam("id", fakeId)
        .body(updatedOrg)
        .when()
        .put(BASE_PATH + "/{id}")
        .then()
        .statusCode(404);
  }

  @Test
  @Order(9)
  @TestSecurity(user = "superadmin@unionflow.com", roles = { "SUPER_ADMIN" })
  @DisplayName("DELETE /api/organisations/{id} doit supprimer une organisation (SUPER_ADMIN)")
  void testSupprimerOrganisation() {
    // Créer une organisation temporaire pour la suppression
    Organisation tempOrg = Organisation.builder()
        .nom("Organisation à Supprimer")
        .typeOrganisation("ASSOCIATION")
        .statut("ACTIVE")
        .email("temp-delete-" + System.currentTimeMillis() + "@test.com")
        .build();
    tempOrg.setDateCreation(LocalDateTime.now());
    tempOrg.setActif(true);
    organisationRepository.persist(tempOrg);

    UUID tempId = tempOrg.getId();

    given()
        .pathParam("id", tempId)
        .when()
        .delete(BASE_PATH + "/{id}")
        .then()
        .statusCode(204);

    // Vérifier que l'organisation a été supprimée (suppression logique : actif =
    // false)
    Organisation deleted = organisationRepository.findById(tempId);
    assert deleted != null : "L'organisation devrait toujours exister dans la base";
    assert !deleted.getActif() : "L'organisation devrait être désactivée (actif = false)";
  }

  @Test
  @Order(10)
  @TestSecurity(user = "superadmin@unionflow.com", roles = { "SUPER_ADMIN" })
  @DisplayName("DELETE /api/organisations/{id} doit retourner 404 pour ID inexistant (SUPER_ADMIN)")
  void testSupprimerOrganisationInexistante() {
    UUID fakeId = UUID.randomUUID();

    given()
        .pathParam("id", fakeId)
        .when()
        .delete(BASE_PATH + "/{id}")
        .then()
        .statusCode(404);
  }

  @Test
  @Order(11)
  @TestSecurity(user = "membre@unionflow.com", roles = { "MEMBRE" })
  @DisplayName("GET /api/organisations doit être accessible aux membres")
  void testListerOrganisationsPourMembre() {
    given()
        .when()
        .get(BASE_PATH)
        .then()
        .statusCode(200);
  }

  @Test
  @Order(12)
  @DisplayName("GET /api/organisations en tant qu'ADMIN_ORGANISATION (onlyOrgAdmin=true, pas ADMIN/SUPER_ADMIN) → couvre branche true")
  @TestSecurity(user = "orgadmin@test.com", roles = { "ADMIN_ORGANISATION" })
  void testListerOrganisations_onlyOrgAdmin_returns200() {
    given()
        .when()
        .get(BASE_PATH)
        .then()
        .statusCode(200);
  }

  @Test
  @Order(12)
  @DisplayName("GET /api/organisations en tant qu'ADMIN_ORGANISATION avec recherche non-vide → couvre branche recherche != null && !isEmpty()")
  @TestSecurity(user = "orgadmin@test.com", roles = { "ADMIN_ORGANISATION" })
  void testListerOrganisations_onlyOrgAdmin_avecRecherche_returns200() {
    given()
        .queryParam("recherche", "Test")
        .when()
        .get(BASE_PATH)
        .then()
        .statusCode(200);
  }

  @Test
  @Order(12)
  @DisplayName("GET /api/organisations publiquement")
  void testListerOrganisationsPublic() {
    given()
        .when()
        .get(BASE_PATH)
        .then()
        .statusCode(200);
  }

  @Test
  @Order(13)
  @TestSecurity(user = "membre@unionflow.com", roles = { "MEMBRE" })
  @DisplayName("POST /api/organisations doit retourner 403 pour un membre (SUPER_ADMIN requis)")
  void testCreerOrganisationPourMembre() {
    java.util.Map<String, Object> newOrg = new java.util.HashMap<>();
    newOrg.put("nom", "Organisation Créée par Membre");
    newOrg.put("typeOrganisation", "ASSOCIATION");
    newOrg.put("statut", "ACTIVE");
    newOrg.put("email", "membre-org-" + System.currentTimeMillis() + "@test.com");

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