package com.anilallewar.microservices.task.oauth2.security;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.test.context.support.WithSecurityContextFactory;

/**
 * Security context factory that will help us in mocking up the OAuth2 workflow
 * so that we can embed an mock access token in the security context.<br>
 * <br>
 * 
 * @author anilallewar
 *
 */
public class WithOAuth2MockAccessTokenSecurityContextFactory
		implements WithSecurityContextFactory<WithMockOAuth2Token> {

	// Default OAuth2 access token
	private static final String TEST_ACCESS_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTgwMDA0MDEsInVzZXJfbmFtZSI6ImFuaWwiLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIiwiUk9MRV9VU0VSIl0sImp0aSI6IjkzY2Y3Y2U0LWY2ZDgtNGJkNi04NGE5LWQ4NDViYmEwZGY2ZCIsImNsaWVudF9pZCI6ImFjbWUiLCJzY29wZSI6WyJvcGVuaWQiXX0.NdAHIna-8GCRGmvaDqO5iOGy3gkjaVaAZqXkDvpjKiQqSVrM_1j1xvPBwuy2iWjyD_crkw_zehE_jKfLpcLxw2JLJZPOtLaUeCYs6euUvboOAVKpmg62mi81PgV3tpEBaqSi5MmATtVcerXFf64LgS1ZPnsw8WIogGlqkhziSzOR4yH2tAzY0aIheL7AWxAgxfBe6I7Lej9ld1Fx6xHodIz8TzmSD-wlZ18e40WBpd_0wc7M8VE_uK18f39btM2VS02FxVm9fdxxwwDXzDT-ODJOs0L_NoKHmZx-JF72bjNUmac81ZcH4fU-fVdme5b-oJowFPgfhZZZ4_nKFv71Ww";

	@Autowired
	MockHttpSession session;

	@Override
	public SecurityContext createSecurityContext(WithMockOAuth2Token withMockOAuth2Token) {
		
		SecurityContext context = SecurityContextHolder.createEmptyContext();
		Authentication authentication = this.getOauthTestAuthentication(withMockOAuth2Token);
		context.setAuthentication(authentication);

		this.session.setAttribute("scopedTarget.oauth2ClientContext", this.getOauth2ClientContext());
		return context;
	}

	/**
	 * Create the authentication object that we need to setup in context
	 * 
	 * @param withMockOAuth2Token
	 * @return
	 */
	private Authentication getOauthTestAuthentication(WithMockOAuth2Token withMockOAuth2Token) {
		return new OAuth2Authentication(getOauth2Request(withMockOAuth2Token), getAuthentication(withMockOAuth2Token));
	}

	/**
	 * Mock OAuth2Request
	 * 
	 * @param withMockOAuth2Token
	 * @return
	 */
	private OAuth2Request getOauth2Request(WithMockOAuth2Token withMockOAuth2Token) {
		String clientId = withMockOAuth2Token.clientId();
		Map<String, String> requestParameters = Collections.emptyMap();
		boolean approved = true;
		String redirectUrl = withMockOAuth2Token.redirectUrl();
		Set<String> responseTypes = Collections.emptySet();
		Set<String> scopes = new HashSet<>(Arrays.asList(withMockOAuth2Token.scopes()));
		Set<String> resourceIds = Collections.emptySet();
		Map<String, Serializable> extensionProperties = Collections.emptyMap();
		List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(withMockOAuth2Token.authorities());

		OAuth2Request oAuth2Request = new OAuth2Request(requestParameters, clientId, authorities, approved, scopes,
				resourceIds, redirectUrl, responseTypes, extensionProperties);

		return oAuth2Request;
	}

	/**
	 * Provide the mock user information to be used
	 * 
	 * @param withMockOAuth2Token
	 * @return
	 */
	private Authentication getAuthentication(WithMockOAuth2Token withMockOAuth2Token) {
		List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(withMockOAuth2Token.authorities());

		User userPrincipal = new User(withMockOAuth2Token.userName(), withMockOAuth2Token.password(), true, true, true,
				true, authorities);

		HashMap<String, String> details = new HashMap<String, String>();
		details.put("user_name", withMockOAuth2Token.userName());
		details.put("email", "[email protected]");
		details.put("name", "Anil Allewar");

		TestingAuthenticationToken token = new TestingAuthenticationToken(userPrincipal, null, authorities);
		token.setAuthenticated(true);
		token.setDetails(details);

		return token;
	}

	/**
	 * Create the mock {@link OAuth2ClientContext} object that will be injected
	 * into the session associated with the request.<br>
	 * <br>
	 * 
	 * Without this object in the session, Spring security will attempt to make
	 * a request to obtain the token if the controller object attempts to use it
	 * (as in our case)
	 * 
	 * @return
	 */
	private OAuth2ClientContext getOauth2ClientContext() {
		OAuth2ClientContext mockClient = mock(OAuth2ClientContext.class);
		when(mockClient.getAccessToken()).thenReturn(new DefaultOAuth2AccessToken(TEST_ACCESS_TOKEN));

		return mockClient;
	}
}