package org.gcube.keycloak.protocol.oidc.mapper;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

import org.junit.Test;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionContext;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oidc.mappers.FullNameMapper;
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.AccessToken;
import org.mockito.Mockito;

import jakarta.ws.rs.core.HttpHeaders;

/**
 * Original code repo: https://github.com/mschwartau/keycloak-custom-protocol-mapper-example
 */
public class D4ScienceContextMapperTest {

    static final String HEADER_VALUE = "ginostilla";

    @Test
    public void shouldTokenMapperDisplayCategory() {
        final String tokenMapperDisplayCategory = new FullNameMapper().getDisplayCategory();
        assertThat(new D4ScienceContextMapper().getDisplayCategory()).isEqualTo(tokenMapperDisplayCategory);
    }

    @Test
    public void shouldHaveDisplayType() {
        assertThat(new D4ScienceContextMapper().getDisplayType()).isNotBlank();
    }

    @Test
    public void shouldHaveHelpText() {
        assertThat(new D4ScienceContextMapper().getHelpText()).isNotBlank();
    }

    @Test
    public void shouldHaveIdId() {
        assertThat(new D4ScienceContextMapper().getId()).isNotBlank();
    }

    @Test
    public void shouldHaveProperties() {
        final List<String> configPropertyNames = new D4ScienceContextMapper().getConfigProperties().stream()
                .map(ProviderConfigProperty::getName)
                .collect(Collectors.toList());

        assertThat(configPropertyNames).containsExactlyInAnyOrder(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME,
                OIDCAttributeMapperHelper.INCLUDE_IN_LIGHTWEIGHT_ACCESS_TOKEN,
                OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, D4ScienceContextMapper.HTTP_REQUEST_HEADER_NAME,
                D4ScienceContextMapper.NARROW_RESOURCE_ACCESS);
    }

    @Test
    public void shouldAddClaimAndNotNarrow() {
        final UserSessionModel session = givenUserSession();
        final KeycloakSession keycloakSession = givenKeycloakSession(true);
        final AccessToken accessToken = transformAccessToken(session, keycloakSession, true, false);
        assertThat(accessToken.getAudience()[0]).isEqualTo(HEADER_VALUE);
        assertThat(accessToken.getResourceAccess().size()).isEqualTo(2);
        assertThat(accessToken.getResourceAccess().keySet()).contains(HEADER_VALUE);
    }

    @Test
    public void shouldAddClaimAndNarrow() {
        final UserSessionModel session = givenUserSession();
        final KeycloakSession keycloakSession = givenKeycloakSession(true);
        final AccessToken accessToken = transformAccessToken(session, keycloakSession, true, true);
        assertThat(accessToken.getAudience()[0]).isEqualTo(HEADER_VALUE);
        assertThat(accessToken.getResourceAccess().size()).isEqualTo(1);
        assertThat(accessToken.getResourceAccess().keySet().iterator().next()).isEqualTo(HEADER_VALUE);
    }

    @Test
    public void shouldNotAddClaim() {
        final UserSessionModel session = givenUserSession();
        final KeycloakSession keycloakSession = givenKeycloakSession(false);
        final AccessToken accessToken = transformAccessToken(session, keycloakSession, true, false);
        assertThat(accessToken.getAudience()).isNull();
    }

    @Test
    public void shouldNotAddClaimAndLogWarning() {
        final UserSessionModel session = givenUserSession();
        final KeycloakSession keycloakSession = givenKeycloakSession(true);
        final AccessToken accessToken = transformAccessToken(session, keycloakSession, false, false);
        assertThat(accessToken.getAudience()).isNull();
    }

    private UserSessionModel givenUserSession() {
        UserSessionModel userSession = Mockito.mock(UserSessionModel.class);
        UserModel user = Mockito.mock(UserModel.class);
        when(userSession.getUser()).thenReturn(user);
        return userSession;
    }

    private KeycloakSession givenKeycloakSession(boolean withHeader) {
        ClientModel clientModel = Mockito.mock(ClientModel.class);
        when(clientModel.getName()).thenReturn("test-client-id");
        KeycloakContext context = Mockito.mock(KeycloakContext.class);
        when(context.getClient()).thenReturn(clientModel);
        KeycloakSession keycloakSession = Mockito.mock(KeycloakSession.class);
        when(keycloakSession.getContext()).thenReturn(context);
        HttpHeaders headers = Mockito.mock(HttpHeaders.class);
        when(context.getRequestHeaders()).thenReturn(headers);

        if (withHeader) {
            when(headers.getHeaderString(D4ScienceContextMapper.DEFAULT_HEADER_NAME)).thenReturn(HEADER_VALUE);
        } else {
            when(headers.getHeaderString(D4ScienceContextMapper.DEFAULT_HEADER_NAME)).thenReturn("");
        }
        return keycloakSession;
    }

    private AccessToken transformAccessToken(UserSessionModel userSessionModel, KeycloakSession keycloakSession,
            boolean withResourceAccess, boolean withNarrowRA) {

        final ProtocolMapperModel mappingModel = new ProtocolMapperModel();
        mappingModel.setConfig(createConfig(withNarrowRA));
        AccessToken at = new AccessToken();
        if (withResourceAccess) {
            at.addAccess(HEADER_VALUE);
            at.addAccess(UUID.randomUUID().toString());
        }

        return new D4ScienceContextMapper().transformAccessToken(at, mappingModel, keycloakSession,
                userSessionModel, givenClientSessionContext(keycloakSession));
    }

    private ClientSessionContext givenClientSessionContext(KeycloakSession keycloakSession) {
        AuthenticatedClientSessionModel acsm = Mockito.mock(AuthenticatedClientSessionModel.class);
        ClientModel clientModel = keycloakSession.getContext().getClient();
        when(acsm.getClient()).thenReturn(clientModel);
        ClientSessionContext csc = Mockito.mock(ClientSessionContext.class);
        when(csc.getClientSession()).thenReturn(acsm);
        return csc;
    }

    private Map<String, String> createConfig(boolean narrowRA) {
        final Map<String, String> config = new HashMap<>();
        config.put(D4ScienceContextMapper.HTTP_REQUEST_HEADER_NAME, D4ScienceContextMapper.DEFAULT_HEADER_NAME);
        config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, D4ScienceContextMapper.DEFAULT_TOKEN_CLAIM);
        config.put(D4ScienceContextMapper.NARROW_RESOURCE_ACCESS, Boolean.toString(narrowRA));
        config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
        return config;
    }

}