package dev.lions.unionflow.server.security;

import io.quarkus.security.identity.SecurityIdentity;
import jakarta.interceptor.InvocationContext;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import org.junit.jupiter.api.BeforeEach;
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.lang.reflect.Method;
import java.util.Set;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class RlsContextInterceptorTest {

    @InjectMocks
    RlsContextInterceptor interceptor;

    @Mock
    EntityManager em;

    @Mock
    OrganisationContextHolder contextHolder;

    @Mock
    SecurityIdentity identity;

    @Mock
    InvocationContext invocationContext;

    @BeforeEach
    void setUp() throws Exception {
        // ensure the proceed returns a value
        when(invocationContext.proceed()).thenReturn("result");
    }

    @Test
    void aroundInvoke_whenIdentityNull_proceedsWithoutRls() throws Exception {
        Field identityField = RlsContextInterceptor.class.getDeclaredField("identity");
        identityField.setAccessible(true);
        identityField.set(interceptor, null);

        Object result = interceptor.applyRlsContext(invocationContext);

        assertThat(result).isEqualTo("result");
        verify(em, never()).createNativeQuery(anyString());
    }

    @Test
    void aroundInvoke_whenAnonymous_proceedsWithoutRls() throws Exception {
        when(identity.isAnonymous()).thenReturn(true);

        Object result = interceptor.applyRlsContext(invocationContext);

        assertThat(result).isEqualTo("result");
        verify(em, never()).createNativeQuery(anyString());
    }

    @Test
    void aroundInvoke_whenAuthenticated_executesSetConfig() throws Exception {
        when(identity.isAnonymous()).thenReturn(false);
        when(identity.getRoles()).thenReturn(Set.of("ADMIN"));
        when(contextHolder.hasContext()).thenReturn(false);

        Query query = mock(Query.class);
        when(em.createNativeQuery(anyString())).thenReturn(query);
        when(query.setParameter(anyString(), anyString())).thenReturn(query);
        when(query.getSingleResult()).thenReturn(new Object[]{});

        Object result = interceptor.applyRlsContext(invocationContext);

        assertThat(result).isEqualTo("result");
        verify(em).createNativeQuery(anyString());
    }

    @Test
    void aroundInvoke_whenAuthenticatedWithOrg_setsOrgId() throws Exception {
        when(identity.isAnonymous()).thenReturn(false);
        when(identity.getRoles()).thenReturn(Set.of("ADMIN"));

        UUID orgId = UUID.randomUUID();
        when(contextHolder.hasContext()).thenReturn(true);
        when(contextHolder.getOrganisationId()).thenReturn(orgId);

        Query query = mock(Query.class);
        when(em.createNativeQuery(anyString())).thenReturn(query);
        when(query.setParameter(anyString(), anyString())).thenReturn(query);
        when(query.getSingleResult()).thenReturn(new Object[]{});

        Object result = interceptor.applyRlsContext(invocationContext);

        assertThat(result).isEqualTo("result");
    }

    @Test
    void aroundInvoke_whenSuperAdmin_setsSuperAdminFlag() throws Exception {
        when(identity.isAnonymous()).thenReturn(false);
        when(identity.getRoles()).thenReturn(Set.of("SUPER_ADMIN"));
        when(contextHolder.hasContext()).thenReturn(false);

        Query query = mock(Query.class);
        when(em.createNativeQuery(anyString())).thenReturn(query);
        when(query.setParameter(anyString(), anyString())).thenReturn(query);
        when(query.getSingleResult()).thenReturn(new Object[]{});

        assertThatCode(() -> interceptor.applyRlsContext(invocationContext))
                .doesNotThrowAnyException();
    }

    @Test
    void aroundInvoke_whenSuperadminAlternateSpelling_setsSuperAdminFlag() throws Exception {
        when(identity.isAnonymous()).thenReturn(false);
        when(identity.getRoles()).thenReturn(Set.of("SUPERADMIN"));
        when(contextHolder.hasContext()).thenReturn(false);

        Query query = mock(Query.class);
        when(em.createNativeQuery(anyString())).thenReturn(query);
        when(query.setParameter(anyString(), anyString())).thenReturn(query);
        when(query.getSingleResult()).thenReturn(new Object[]{});

        assertThatCode(() -> interceptor.applyRlsContext(invocationContext))
                .doesNotThrowAnyException();
    }

    @Test
    void aroundInvoke_whenEmThrows_continuesProceed() throws Exception {
        when(identity.isAnonymous()).thenReturn(false);
        when(identity.getRoles()).thenReturn(Set.of("MEMBRE"));
        when(contextHolder.hasContext()).thenReturn(false);
        when(em.createNativeQuery(anyString())).thenThrow(new RuntimeException("Hors transaction"));

        Object result = interceptor.applyRlsContext(invocationContext);

        // Must still proceed despite the exception
        assertThat(result).isEqualTo("result");
        verify(invocationContext).proceed();
    }

    @Test
    void aroundInvoke_whenRolesNull_handlesGracefully() throws Exception {
        when(identity.isAnonymous()).thenReturn(false);
        when(identity.getRoles()).thenReturn(null);
        when(contextHolder.hasContext()).thenReturn(false);

        Query query = mock(Query.class);
        when(em.createNativeQuery(anyString())).thenReturn(query);
        when(query.setParameter(anyString(), anyString())).thenReturn(query);
        when(query.getSingleResult()).thenReturn(new Object[]{});

        assertThatCode(() -> interceptor.applyRlsContext(invocationContext)).doesNotThrowAnyException();
    }
}
