package eu.eudat.logic.security.validators.configurableProvider;

import eu.eudat.exceptions.security.NullEmailException;
import eu.eudat.logic.proxy.config.configloaders.ConfigLoader;

import eu.eudat.logic.security.customproviders.ConfigurableProvider.ConfigurableProviderCustomProvider;
import eu.eudat.logic.security.customproviders.ConfigurableProvider.ConfigurableProviderUser;
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.ConfigurableProvider;
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.oauth2.Oauth2ConfigurableProvider;
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2.Saml2ConfigurableProvider;
import eu.eudat.logic.security.validators.TokenValidator;
import eu.eudat.logic.security.validators.configurableProvider.helpers.ConfigurableProviderRequest;
import eu.eudat.logic.security.validators.configurableProvider.helpers.ConfigurableProviderResponseToken;
import eu.eudat.logic.services.operations.authentication.AuthenticationService;
import eu.eudat.models.data.login.LoginInfo;
import eu.eudat.models.data.loginprovider.LoginProviderUser;
import eu.eudat.models.data.security.Principal;

import org.opensaml.saml.saml2.core.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

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

@Component("configurableProviderTokenValidator")
public class ConfigurableProviderTokenValidator implements TokenValidator {

	private static final Logger logger = LoggerFactory.getLogger(ConfigurableProviderTokenValidator.class);

	private ConfigurableProviderCustomProvider configurableProvider;
	private AuthenticationService nonVerifiedUserAuthenticationService;
	private ConfigLoader configLoader;

	public ConfigurableProviderTokenValidator(ConfigurableProviderCustomProvider configurableProvider, AuthenticationService nonVerifiedUserAuthenticationService, ConfigLoader configLoader) {
		this.configurableProvider = configurableProvider;
		this.nonVerifiedUserAuthenticationService = nonVerifiedUserAuthenticationService;
		this.configLoader = configLoader;
	}

	public ConfigurableProviderResponseToken getAccessToken(ConfigurableProviderRequest configurableProviderRequest) {
		Oauth2ConfigurableProvider provider = (Oauth2ConfigurableProvider)getConfigurableProviderFromId(configurableProviderRequest.getConfigurableLoginId());
		return this.configurableProvider.getAccessToken(configurableProviderRequest.getCode(),
				provider.getRedirect_uri(), provider.getClientId(), provider.getClientSecret(),
				provider.getAccess_token_url(), provider.getGrant_type(), provider.getToken().getAccess_token(), provider.getToken().getExpires_in());
	}
	@Override
	public Principal validateToken(LoginInfo credentials) throws NullEmailException {
		String configurableLoginId = ((Map) credentials.getData()).get("configurableLoginId").toString();
		ConfigurableProvider configurableProvider = getConfigurableProviderFromId(configurableLoginId);

		LoginProviderUser user = new LoginProviderUser();
		if (configurableProvider.getType().equals("oauth2")) {
			ConfigurableProviderUser configurableUser = this.configurableProvider.getUser(credentials.getTicket(), ((Oauth2ConfigurableProvider)configurableProvider).getUser());
			user.setId(configurableUser.getId());
			user.setEmail(configurableUser.getEmail());
			user.setName(configurableUser.getName());
			user.setProvider(credentials.getProvider());
			user.setSecret(credentials.getTicket());
			return this.nonVerifiedUserAuthenticationService.Touch(user);
		}
		else if (configurableProvider.getType().equals("saml2")) {

			Assertion saml2Assertion = null;
			try {
				Saml2ConfigurableProvider saml2Provider = (Saml2ConfigurableProvider)configurableProvider;
				if(saml2Provider.getBinding().equals("Redirect") || saml2Provider.getBinding().equals("Post"))
					saml2Assertion = Saml2SSOUtils.processResponse(credentials.getTicket(), saml2Provider);
				else if(saml2Provider.getBinding().equals("Artifact"))
					saml2Assertion = Saml2SSOUtils.processArtifactResponse(credentials.getTicket(), saml2Provider);
			} catch (Exception e) {
				logger.error(e.getMessage(), e);
			}

			if(saml2Assertion == null)
				return null;

			List<AttributeStatement> attributeStatements = saml2Assertion.getAttributeStatements();
			if(attributeStatements != null &&  !attributeStatements.isEmpty()){

				List<Attribute> attributes = attributeStatements.get(0).getAttributes();
				if(attributes != null && !attributes.isEmpty()){

					Saml2ConfigurableProvider.SAML2UsingFormat usingFormat = ((Saml2ConfigurableProvider)configurableProvider).getUsingFormat();
					Map<String, String> attributeMapping = ((Saml2ConfigurableProvider)configurableProvider).getConfigurableUserFromAttributes();
					Map<String, Saml2ConfigurableProvider.SAML2AttributeType> attributeType = ((Saml2ConfigurableProvider)configurableProvider).getAttributeTypes();
					Map<String, Object> saml2User = new HashMap<>();
					for(Attribute attribute: attributes){

						String attributeName = Saml2SSOUtils.getAttributeName(attribute, usingFormat);
						if(attributeName != null && attributeMapping.containsValue(attributeName)){

							Saml2ConfigurableProvider.SAML2AttributeType attrType = attributeType.get(attributeName);
							if(attribute.getAttributeValues() != null && !attribute.getAttributeValues().isEmpty() && attrType != null){
								Object attributeValue = Saml2SSOUtils.getAttributeType(attribute.getAttributeValues().get(0), attrType);
								if(attributeValue != null) {
									saml2User.put(attributeName, attributeValue);
								}
							}

						}

					}

					try{
						String subjectNameId = saml2Assertion.getSubject().getNameID().getValue();
						String userId = configurableLoginId + ": " + subjectNameId;
						user.setId(userId);
					} catch(NullPointerException e){
						logger.error("Could not get Subject NameID value of assertion");
						return null;
					}
					user.setEmail((String)saml2User.get(attributeMapping.get("email")));
					user.setName((String)saml2User.get(attributeMapping.get("name")));
					user.setProvider(credentials.getProvider());
					user.setSecret(credentials.getTicket());

				}

			}
			else
				return null;

			return this.nonVerifiedUserAuthenticationService.Touch(user);

		}
		return null;
	}

	private ConfigurableProvider getConfigurableProviderFromId(String configurableId) {
		return this.configLoader.getConfigurableProviders().getProviders().stream()
				.filter(prov -> prov.getConfigurableLoginId().equals(configurableId))
				.collect(Collectors.toList())
				.get(0);
	}
}
