package org.apereo.cas.adaptors.azure;

import net.phonefactor.pfsdk.PFAuth;
import net.phonefactor.pfsdk.PFAuthParams;
import net.phonefactor.pfsdk.PFAuthResult;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.HandlerResult;
import org.apereo.cas.authentication.PreventedException;
import org.apereo.cas.authentication.handler.support.AbstractPreAndPostProcessingAuthenticationHandler;
import org.apereo.cas.authentication.principal.Principal;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.web.support.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.RequestContextHolder;

import javax.security.auth.login.FailedLoginException;
import java.security.GeneralSecurityException;

/**
 * An authentication handler that uses the token provided
 * to authenticator against azure authN for MFA.
 *
 * @author Misagh Moayyed
 * @since 5.1.0
 */
public class AzureAuthenticatorAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(AzureAuthenticatorAuthenticationHandler.class);
    
    private final PFAuth azureAuthenticatorInstance;
    private final AzureAuthenticatorAuthenticationRequestBuilder authenticationRequestBuilder;

    public AzureAuthenticatorAuthenticationHandler(final String name, final ServicesManager servicesManager, final PrincipalFactory principalFactory,
                                                   final PFAuth azureAuthenticatorInstance, final AzureAuthenticatorAuthenticationRequestBuilder builder) {
        super(name, servicesManager, principalFactory, null);
        this.azureAuthenticatorInstance = azureAuthenticatorInstance;
        this.authenticationRequestBuilder = builder;
    }

    @Override
    protected HandlerResult doAuthentication(final Credential credential) throws GeneralSecurityException, PreventedException {
        try {
            final AzureAuthenticatorTokenCredential c = (AzureAuthenticatorTokenCredential) credential;
            final RequestContext context = RequestContextHolder.getRequestContext();
            final Principal principal = WebUtils.getAuthentication(context).getPrincipal();

            LOGGER.debug("Received principal id [{}]", principal.getId());
            final PFAuthParams params = authenticationRequestBuilder.build(principal, c);
            final PFAuthResult r = azureAuthenticatorInstance.authenticate(params);

            if (r.getAuthenticated()) {
                return createHandlerResult(c, principalFactory.createPrincipal(principal.getId()), null);
            }
            LOGGER.error("Authentication failed. Call status: [{}]-[{}]. Error: [{}]", r.getCallStatus(),
                    r.getCallStatusString(), r.getMessageError());

        } catch (final Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
        throw new FailedLoginException("Failed to authenticate user");
    }

    @Override
    public boolean supports(final Credential credential) {
        return AzureAuthenticatorTokenCredential.class.isAssignableFrom(credential.getClass());
    }
}
