package dev.lions.unionflow.server.security;

import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.quarkus.hibernate.orm.panache.PanacheQuery;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.ForbiddenException;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.lang.reflect.Field;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
@DisplayName("Tests OrganisationContextResolver")
class OrganisationContextResolverTest {

    @Mock
    JsonWebToken jwt;

    @Mock
    OrganisationRepository organisationRepository;

    @InjectMocks
    OrganisationContextResolver resolver;

    @Mock
    @SuppressWarnings("rawtypes")
    PanacheQuery panacheQuery;

    private final UUID kcOrgId = UUID.randomUUID();
    private final UUID orgId = UUID.randomUUID();

    @BeforeEach
    void injectMocks() throws Exception {
        Field jwtField = OrganisationContextResolver.class.getDeclaredField("jwt");
        jwtField.setAccessible(true);
        jwtField.set(resolver, jwt);

        Field repoField = OrganisationContextResolver.class.getDeclaredField("organisationRepository");
        repoField.setAccessible(true);
        repoField.set(resolver, organisationRepository);
    }

    // ─── resolveOrganisationId ────────────────────────────────────────────────

    @Test
    @DisplayName("null claim → BadRequestException")
    void resolveOrganisationId_nullClaim_throwsBadRequest() {
        when(jwt.getClaim("organization")).thenReturn(null);
        assertThatThrownBy(() -> resolver.resolveOrganisationId())
                .isInstanceOf(BadRequestException.class);
    }

    @Test
    @DisplayName("empty claim → BadRequestException")
    void resolveOrganisationId_emptyClaim_throwsBadRequest() {
        when(jwt.getClaim("organization")).thenReturn(Map.of());
        assertThatThrownBy(() -> resolver.resolveOrganisationId())
                .isInstanceOf(BadRequestException.class);
    }

    @Test
    @DisplayName("multi-org claim → BadRequestException")
    void resolveOrganisationId_multiOrgClaim_throwsBadRequest() {
        Map<String, Object> multiOrg = Map.of(
                "org-a", Map.of("id", UUID.randomUUID().toString()),
                "org-b", Map.of("id", UUID.randomUUID().toString())
        );
        when(jwt.getClaim("organization")).thenReturn(multiOrg);
        assertThatThrownBy(() -> resolver.resolveOrganisationId())
                .isInstanceOf(BadRequestException.class)
                .hasMessageContaining("multi-organisation");
    }

    @Test
    @DisplayName("entry sans champ id → BadRequestException")
    @SuppressWarnings("unchecked")
    void resolveOrganisationId_entryWithoutId_throwsBadRequest() {
        Map<String, Object> claim = Map.of("mutuelle-gbane", Map.of("name", "Mutuelle GBANE"));
        when(jwt.getClaim("organization")).thenReturn(claim);
        assertThatThrownBy(() -> resolver.resolveOrganisationId())
                .isInstanceOf(BadRequestException.class)
                .hasMessageContaining("malformé");
    }

    @Test
    @DisplayName("entry non-Map → BadRequestException (id null)")
    @SuppressWarnings("unchecked")
    void resolveOrganisationId_entryNotMap_throwsBadRequest() {
        Map<String, Object> claim = Map.of("mutuelle-gbane", "not-a-map");
        when(jwt.getClaim("organization")).thenReturn(claim);
        assertThatThrownBy(() -> resolver.resolveOrganisationId())
                .isInstanceOf(BadRequestException.class);
    }

    @Test
    @DisplayName("id non-UUID → BadRequestException")
    @SuppressWarnings("unchecked")
    void resolveOrganisationId_invalidUuid_throwsBadRequest() {
        Map<String, Object> claim = Map.of("mutuelle-gbane", Map.of("id", "not-a-uuid"));
        when(jwt.getClaim("organization")).thenReturn(claim);
        assertThatThrownBy(() -> resolver.resolveOrganisationId())
                .isInstanceOf(BadRequestException.class)
                .hasMessageContaining("UUID valide");
    }

    @Test
    @DisplayName("org non trouvée en DB → ForbiddenException")
    @SuppressWarnings("unchecked")
    void resolveOrganisationId_orgNotInDb_throwsForbidden() {
        Map<String, Object> claim = Map.of("mutuelle-gbane", Map.of("id", kcOrgId.toString()));
        when(jwt.getClaim("organization")).thenReturn(claim);
        when(organisationRepository.find(eq("keycloakOrgId = ?1 AND actif = true"), eq(kcOrgId)))
                .thenReturn(panacheQuery);
        when(panacheQuery.firstResultOptional()).thenReturn(Optional.empty());

        assertThatThrownBy(() -> resolver.resolveOrganisationId())
                .isInstanceOf(ForbiddenException.class);
    }

    @Test
    @DisplayName("org trouvée → retourne UUID")
    @SuppressWarnings("unchecked")
    void resolveOrganisationId_validToken_returnsId() {
        Map<String, Object> claim = Map.of("mutuelle-gbane", Map.of("id", kcOrgId.toString()));
        when(jwt.getClaim("organization")).thenReturn(claim);
        when(organisationRepository.find(eq("keycloakOrgId = ?1 AND actif = true"), eq(kcOrgId)))
                .thenReturn(panacheQuery);

        Organisation org = new Organisation();
        org.setId(orgId);
        when(panacheQuery.firstResultOptional()).thenReturn(Optional.of(org));

        UUID result = resolver.resolveOrganisationId();
        assertThat(result).isEqualTo(orgId);
    }

    // ─── resolveOrganisationIdIfPresent ──────────────────────────────────────

    @Test
    @DisplayName("claim absent → Optional.empty()")
    void resolveOrganisationIdIfPresent_noClaim_returnsEmpty() {
        when(jwt.getClaim("organization")).thenReturn(null);
        assertThat(resolver.resolveOrganisationIdIfPresent()).isEmpty();
    }

    @Test
    @DisplayName("claim vide → Optional.empty()")
    void resolveOrganisationIdIfPresent_emptyClaim_returnsEmpty() {
        when(jwt.getClaim("organization")).thenReturn(Map.of());
        assertThat(resolver.resolveOrganisationIdIfPresent()).isEmpty();
    }

    @Test
    @DisplayName("org non trouvée (ForbiddenException) → Optional.empty()")
    @SuppressWarnings("unchecked")
    void resolveOrganisationIdIfPresent_forbidden_returnsEmpty() {
        Map<String, Object> claim = Map.of("mutuelle-gbane", Map.of("id", kcOrgId.toString()));
        when(jwt.getClaim("organization")).thenReturn(claim);
        when(organisationRepository.find(eq("keycloakOrgId = ?1 AND actif = true"), eq(kcOrgId)))
                .thenReturn(panacheQuery);
        when(panacheQuery.firstResultOptional()).thenReturn(Optional.empty());

        assertThat(resolver.resolveOrganisationIdIfPresent()).isEmpty();
    }

    @Test
    @DisplayName("claim valide → Optional avec UUID")
    @SuppressWarnings("unchecked")
    void resolveOrganisationIdIfPresent_valid_returnsId() {
        Map<String, Object> claim = Map.of("mutuelle-gbane", Map.of("id", kcOrgId.toString()));
        when(jwt.getClaim("organization")).thenReturn(claim);
        when(organisationRepository.find(eq("keycloakOrgId = ?1 AND actif = true"), eq(kcOrgId)))
                .thenReturn(panacheQuery);

        Organisation org = new Organisation();
        org.setId(orgId);
        when(panacheQuery.firstResultOptional()).thenReturn(Optional.of(org));

        assertThat(resolver.resolveOrganisationIdIfPresent()).contains(orgId);
    }
}
