package dev.lions.unionflow.server.security;

import io.quarkus.security.identity.SecurityIdentity;
import jakarta.ws.rs.container.ContainerRequestContext;
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 javax.sql.DataSource;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Set;
import java.util.UUID;

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

@ExtendWith(MockitoExtension.class)
class RlsConnectionInitializerTest {

    @InjectMocks
    RlsConnectionInitializer initializer;

    @Mock
    OrganisationContextHolder contextHolder;

    @Mock
    SecurityIdentity identity;

    @Mock
    DataSource dataSource;

    @Mock
    ContainerRequestContext requestContext;

    @BeforeEach
    void setUp() throws Exception {
        // @Inject DataSource is injected by @InjectMocks
        // but we also need to ensure the dataSource field is set
        Field dsField = RlsConnectionInitializer.class.getDeclaredField("dataSource");
        dsField.setAccessible(true);
        dsField.set(initializer, dataSource);
    }

    @Test
    void filter_whenIdentityNull_skips() throws Exception {
        Field identityField = RlsConnectionInitializer.class.getDeclaredField("identity");
        identityField.setAccessible(true);
        identityField.set(initializer, null);

        assertThatCode(() -> initializer.filter(requestContext)).doesNotThrowAnyException();

        verify(dataSource, never()).getConnection();
    }

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

        assertThatCode(() -> initializer.filter(requestContext)).doesNotThrowAnyException();

        verify(dataSource, never()).getConnection();
    }

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

        Connection conn = mock(Connection.class);
        PreparedStatement stmt = mock(PreparedStatement.class);
        when(dataSource.getConnection()).thenReturn(conn);
        when(conn.prepareStatement(anyString())).thenReturn(stmt);

        assertThatCode(() -> initializer.filter(requestContext)).doesNotThrowAnyException();

        verify(dataSource).getConnection();
        verify(stmt).execute();
    }

    @Test
    void filter_whenAuthenticatedWithOrgContext_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);

        Connection conn = mock(Connection.class);
        PreparedStatement stmt = mock(PreparedStatement.class);
        when(dataSource.getConnection()).thenReturn(conn);
        when(conn.prepareStatement(anyString())).thenReturn(stmt);

        assertThatCode(() -> initializer.filter(requestContext)).doesNotThrowAnyException();

        verify(stmt).execute();
    }

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

        Connection conn = mock(Connection.class);
        PreparedStatement stmt = mock(PreparedStatement.class);
        when(dataSource.getConnection()).thenReturn(conn);
        when(conn.prepareStatement(anyString())).thenReturn(stmt);

        assertThatCode(() -> initializer.filter(requestContext)).doesNotThrowAnyException();

        verify(stmt).execute();
    }

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

        Connection conn = mock(Connection.class);
        PreparedStatement stmt = mock(PreparedStatement.class);
        when(dataSource.getConnection()).thenReturn(conn);
        when(conn.prepareStatement(anyString())).thenReturn(stmt);

        assertThatCode(() -> initializer.filter(requestContext)).doesNotThrowAnyException();

        verify(stmt).execute();
    }

    @Test
    void filter_whenDbThrows_doesNotPropagate() throws Exception {
        when(identity.isAnonymous()).thenReturn(false);
        when(identity.getRoles()).thenReturn(Set.of("MEMBRE"));
        when(contextHolder.hasContext()).thenReturn(false);
        when(dataSource.getConnection()).thenThrow(new RuntimeException("DB unavailable"));

        // Non-bloquant : doit absorber l'erreur
        assertThatCode(() -> initializer.filter(requestContext)).doesNotThrowAnyException();
    }

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

        Connection conn = mock(Connection.class);
        PreparedStatement stmt = mock(PreparedStatement.class);
        when(dataSource.getConnection()).thenReturn(conn);
        when(conn.prepareStatement(anyString())).thenReturn(stmt);

        assertThatCode(() -> initializer.filter(requestContext)).doesNotThrowAnyException();
    }
}
