package dev.lions.unionflow.server.security;

import dev.lions.unionflow.server.service.OrganisationModuleService;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ResourceInfo;
import jakarta.ws.rs.core.Response;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.UUID;

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

@ExtendWith(MockitoExtension.class)
class ModuleAccessFilterTest {

    @InjectMocks
    ModuleAccessFilter filter;

    @Mock
    OrganisationModuleService organisationModuleService;

    @Mock
    SecurityIdentity identity;

    @Mock
    ResourceInfo resourceInfo;

    @Mock
    ContainerRequestContext requestContext;

    @BeforeEach
    void setUp() throws Exception {
        // Inject @Context ResourceInfo via reflection (not injected by @InjectMocks since it's @Context)
        Field resourceInfoField = ModuleAccessFilter.class.getDeclaredField("resourceInfo");
        resourceInfoField.setAccessible(true);
        resourceInfoField.set(filter, resourceInfo);
    }

    @Test
    void filter_whenNoAnnotationOnMethod_passes() throws Exception {
        Method method = getClass().getDeclaredMethod("noAnnotationMethod");
        when(resourceInfo.getResourceMethod()).thenReturn(method);
        when(resourceInfo.getResourceClass()).thenReturn((Class) getClass());

        filter.filter(requestContext);

        verify(requestContext, never()).abortWith(any());
    }

    @Test
    void filter_whenAnnotationOnMethod_andSuperAdmin_passes() throws Exception {
        Method method = getClass().getDeclaredMethod("annotatedMethod");
        when(resourceInfo.getResourceMethod()).thenReturn(method);
        when(identity.hasRole("SUPER_ADMIN")).thenReturn(true);

        filter.filter(requestContext);

        verify(requestContext, never()).abortWith(any());
    }

    @Test
    void filter_whenAnnotationOnMethod_andModuleActive_passes() throws Exception {
        Method method = getClass().getDeclaredMethod("annotatedMethod");
        when(resourceInfo.getResourceMethod()).thenReturn(method);
        when(identity.hasRole("SUPER_ADMIN")).thenReturn(false);

        UUID orgId = UUID.randomUUID();
        when(requestContext.getHeaderString(ModuleAccessFilter.HEADER_ACTIVE_ORG)).thenReturn(orgId.toString());
        when(organisationModuleService.isModuleActif(orgId, "TONTINE")).thenReturn(true);

        filter.filter(requestContext);

        verify(requestContext, never()).abortWith(any());
    }

    @Test
    void filter_whenAnnotationOnMethod_andModuleInactive_returns403() throws Exception {
        Method method = getClass().getDeclaredMethod("annotatedMethod");
        when(resourceInfo.getResourceMethod()).thenReturn(method);
        when(identity.hasRole("SUPER_ADMIN")).thenReturn(false);

        UUID orgId = UUID.randomUUID();
        when(requestContext.getHeaderString(ModuleAccessFilter.HEADER_ACTIVE_ORG)).thenReturn(orgId.toString());
        when(organisationModuleService.isModuleActif(orgId, "TONTINE")).thenReturn(false);

        filter.filter(requestContext);

        ArgumentCaptor<Response> captor = ArgumentCaptor.forClass(Response.class);
        verify(requestContext).abortWith(captor.capture());
        assertThat(captor.getValue().getStatus()).isEqualTo(403);
    }

    @Test
    void filter_whenAnnotationOnMethod_andNoOrgHeader_returns403() throws Exception {
        Method method = getClass().getDeclaredMethod("annotatedMethod");
        when(resourceInfo.getResourceMethod()).thenReturn(method);
        when(identity.hasRole("SUPER_ADMIN")).thenReturn(false);

        when(requestContext.getHeaderString(ModuleAccessFilter.HEADER_ACTIVE_ORG)).thenReturn(null);

        filter.filter(requestContext);

        ArgumentCaptor<Response> captor = ArgumentCaptor.forClass(Response.class);
        verify(requestContext).abortWith(captor.capture());
        assertThat(captor.getValue().getStatus()).isEqualTo(403);
    }

    @Test
    void filter_whenAnnotationOnMethod_andBlankOrgHeader_returns403() throws Exception {
        Method method = getClass().getDeclaredMethod("annotatedMethod");
        when(resourceInfo.getResourceMethod()).thenReturn(method);
        when(identity.hasRole("SUPER_ADMIN")).thenReturn(false);

        when(requestContext.getHeaderString(ModuleAccessFilter.HEADER_ACTIVE_ORG)).thenReturn("   ");

        filter.filter(requestContext);

        verify(requestContext).abortWith(any());
    }

    @Test
    void filter_whenAnnotationOnMethod_andInvalidUuidHeader_returns403() throws Exception {
        Method method = getClass().getDeclaredMethod("annotatedMethod");
        when(resourceInfo.getResourceMethod()).thenReturn(method);
        when(identity.hasRole("SUPER_ADMIN")).thenReturn(false);

        when(requestContext.getHeaderString(ModuleAccessFilter.HEADER_ACTIVE_ORG)).thenReturn("not-a-uuid");

        filter.filter(requestContext);

        ArgumentCaptor<Response> captor = ArgumentCaptor.forClass(Response.class);
        verify(requestContext).abortWith(captor.capture());
        assertThat(captor.getValue().getStatus()).isEqualTo(403);
    }

    @Test
    void filter_whenAnnotationOnClass_andNoMethodAnnotation_usesClassAnnotation() throws Exception {
        // Method has no annotation, class has it
        Method method = getClass().getDeclaredMethod("noAnnotationMethod");
        when(resourceInfo.getResourceMethod()).thenReturn(method);
        when(resourceInfo.getResourceClass()).thenReturn((Class) AnnotatedClass.class);
        when(identity.hasRole("SUPER_ADMIN")).thenReturn(false);

        UUID orgId = UUID.randomUUID();
        when(requestContext.getHeaderString(ModuleAccessFilter.HEADER_ACTIVE_ORG)).thenReturn(orgId.toString());
        when(organisationModuleService.isModuleActif(orgId, "CREDIT")).thenReturn(true);

        filter.filter(requestContext);

        verify(requestContext, never()).abortWith(any());
    }

    @Test
    void filter_whenAnnotationOnMethod_andCustomMessage_usesCustomMessage() throws Exception {
        Method method = getClass().getDeclaredMethod("annotatedWithCustomMessage");
        when(resourceInfo.getResourceMethod()).thenReturn(method);
        when(identity.hasRole("SUPER_ADMIN")).thenReturn(false);

        UUID orgId = UUID.randomUUID();
        when(requestContext.getHeaderString(ModuleAccessFilter.HEADER_ACTIVE_ORG)).thenReturn(orgId.toString());
        when(organisationModuleService.isModuleActif(orgId, "EPARGNE")).thenReturn(false);

        filter.filter(requestContext);

        verify(requestContext).abortWith(any());
    }

    // ── Helper methods used as reflection targets ────────────────────────────────

    void noAnnotationMethod() {}

    @RequiresModule("TONTINE")
    void annotatedMethod() {}

    @RequiresModule(value = "EPARGNE", message = "Ce service requiert le module Epargne.")
    void annotatedWithCustomMessage() {}

    @RequiresModule("CREDIT")
    static class AnnotatedClass {}
}
