package org.apereo.cas.support.oauth.authenticator;

import org.apereo.cas.authentication.AuthenticationHandler;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.principal.NullPrincipal;
import org.apereo.cas.authentication.principal.Principal;
import org.apereo.cas.authentication.principal.PrincipalResolver;
import org.apereo.cas.services.RegisteredServiceAccessStrategyAuditableEnforcer;
import org.apereo.cas.support.oauth.OAuth20Constants;
import org.apereo.cas.support.oauth.OAuth20GrantTypes;
import org.apereo.cas.support.oauth.services.OAuth20RegisteredServiceCipherExecutor;

import lombok.val;
import org.apereo.services.persondir.IPersonAttributeDao;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junitpioneer.jupiter.RetryingTest;
import org.pac4j.core.context.JEEContext;
import org.pac4j.core.context.session.JEESessionStore;
import org.pac4j.core.credentials.UsernamePasswordCredentials;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.annotation.DirtiesContext;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;

/**
 * This is {@link OAuth20ClientIdClientSecretAuthenticatorTests}.
 *
 * @author Misagh Moayyed
 * @since 6.0.0
 */
@Tag("OAuth")
@DirtiesContext
public class OAuth20ClientIdClientSecretAuthenticatorTests extends BaseOAuth20AuthenticatorTests {
    protected OAuth20ClientIdClientSecretAuthenticator authenticator;

    @BeforeEach
    public void init() {
        authenticator = new OAuth20ClientIdClientSecretAuthenticator(servicesManager,
            serviceFactory,
            new RegisteredServiceAccessStrategyAuditableEnforcer(),
            new OAuth20RegisteredServiceCipherExecutor(),
            ticketRegistry,
            defaultPrincipalResolver);
    }

    @RetryingTest(3)
    public void verifyAuthentication() {
        val credentials = new UsernamePasswordCredentials("client", "secret");
        val request = new MockHttpServletRequest();
        val ctx = new JEEContext(request, new MockHttpServletResponse());
        authenticator.validate(credentials, ctx, JEESessionStore.INSTANCE);
        assertNotNull(credentials.getUserProfile());
        assertEquals("client", credentials.getUserProfile().getId());
    }

    @Test
    public void verifyAuthenticationWithGrantTypePassword() {
        val credentials = new UsernamePasswordCredentials("client", "secret");
        val request = new MockHttpServletRequest();
        val ctx = new JEEContext(request, new MockHttpServletResponse());

        request.addParameter(OAuth20Constants.GRANT_TYPE, OAuth20GrantTypes.PASSWORD.name());

        authenticator.validate(credentials, ctx, JEESessionStore.INSTANCE);
        assertNull(credentials.getUserProfile());
    }

    @Test
    public void verifyAuthenticationWithGrantTypeRefreshToken() {
        val refreshToken = getRefreshToken(serviceWithoutSecret);
        ticketRegistry.addTicket(refreshToken);
        
        val credentials = new UsernamePasswordCredentials("serviceWithoutSecret", refreshToken.getId());
        val request = new MockHttpServletRequest();
        val ctx = new JEEContext(request, new MockHttpServletResponse());

        request.addParameter(OAuth20Constants.GRANT_TYPE, OAuth20GrantTypes.REFRESH_TOKEN.name());
        request.addParameter(OAuth20Constants.CLIENT_ID, "serviceWithoutSecret");
        request.addParameter(OAuth20Constants.REFRESH_TOKEN, refreshToken.getId());

        assertFalse(authenticator.canAuthenticate(ctx));
        authenticator.validate(credentials, ctx, JEESessionStore.INSTANCE);
        assertNull(credentials.getUserProfile());


        request.removeAllParameters();
        request.addParameter(OAuth20Constants.GRANT_TYPE, OAuth20GrantTypes.REFRESH_TOKEN.name());
        request.addParameter(OAuth20Constants.REFRESH_TOKEN, refreshToken.getId());
        assertTrue(authenticator.canAuthenticate(ctx));


        request.removeAllParameters();
        request.addParameter(OAuth20Constants.GRANT_TYPE, OAuth20GrantTypes.REFRESH_TOKEN.name());
        request.addParameter(OAuth20Constants.CLIENT_ID, "serviceWithoutSecret");
        request.addParameter(OAuth20Constants.CLIENT_SECRET, "serviceWithoutSecret");
        request.addParameter(OAuth20Constants.REFRESH_TOKEN, refreshToken.getId());
        assertTrue(authenticator.canAuthenticate(ctx));
    }

    @Test
    public void verifyAuthenticationWithAttributesMapping() {

        val credentials = new UsernamePasswordCredentials("serviceWithAttributesMapping", "secret");
        val request = new MockHttpServletRequest();
        val ctx = new JEEContext(request, new MockHttpServletResponse());
        authenticator.validate(credentials, ctx, JEESessionStore.INSTANCE);
        assertNotNull(credentials.getUserProfile());
        assertEquals("servicewithattributesmapping", credentials.getUserProfile().getId());
        assertNotNull(credentials.getUserProfile().getAttribute("eduPersonAffiliation"));
        assertNull(credentials.getUserProfile().getAttribute("groupMembership"));
    }


    @Test
    public void verifyAuthenticationWithoutResolvedPrincipal() {

        val id = "serviceWithAttributesMapping";

        val nullPrincipalAuthenticator = new OAuth20ClientIdClientSecretAuthenticator(servicesManager,
                serviceFactory,
                new RegisteredServiceAccessStrategyAuditableEnforcer(),
                new OAuth20RegisteredServiceCipherExecutor(),
                ticketRegistry,
                new PrincipalResolver() {
                    @Override
                    public Principal resolve(Credential credential, Optional<Principal> principal, Optional<AuthenticationHandler> handler) {
                        return new NullPrincipal();
                    }

                    @Override
                    public boolean supports(Credential credential) {
                        return true;
                    }

                    @Override
                    public IPersonAttributeDao getAttributeRepository() {
                        return null;
                    }
                });

        val credentials = new UsernamePasswordCredentials(id, "secret");
        val request = new MockHttpServletRequest();
        val ctx = new JEEContext(request, new MockHttpServletResponse());
        nullPrincipalAuthenticator.validate(credentials, ctx, JEESessionStore.INSTANCE);
        assertNotNull(credentials.getUserProfile());
        assertEquals(id, credentials.getUserProfile().getId());
    }


}

@@ -1268,16 +1268,144 @@
 tions.*;
+%0Aimport static org.mockito.ArgumentMatchers.any;%0Aimport static org.mockito.Mockito.mock;%0Aimport static org.mockito.Mockito.when;
 %0A%0A/**%0A *
@@ -4734,25 +4734,24 @@
 Mapping() %7B%0A
-%0A
         val 
@@ -5365,17 +5365,16 @@
 %0A    %7D%0A%0A
-%0A
     @Tes
@@ -5440,17 +5440,16 @@
 pal() %7B%0A
-%0A
         
@@ -5488,16 +5488,254 @@
 apping%22;
+%0A        val credentials = new UsernamePasswordCredentials(id, %22secret%22);%0A        PrincipalResolver mockPrincipalResolver = mock(PrincipalResolver.class);%0A        when(mockPrincipalResolver.resolve(any())).thenReturn(new NullPrincipal());
 %0A%0A      
@@ -6049,697 +6049,33 @@
     
-new PrincipalResolver() %7B%0A                    @Override%0A                    public Principal resolve(Credential credential, Optional%3CPrincipal%3E principal, Optional%3CAuthenticationHandler%3E handler) %7B%0A                        return new NullPrincipal();%0A                    %7D%0A%0A                    @Override%0A                    public boolean supports(Credential credential) %7B%0A                        return true;%0A                    %7D%0A%0A                    @Override%0A                    public IPersonAttributeDao getAttributeRepository() %7B%0A                        return null;%0A                    %7D%0A                %7D);%0A%0A        val credentials = new UsernamePasswordCredentials(id, %22secret%22
+mockPrincipalResolver
 );%0A
+%0A
     
@@ -6408,12 +6408,10 @@
 ;%0A    %7D%0A
-%0A%0A
 %7D%0A

use mock instead of inline implementation