package org.apereo.cas.authentication;

import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
import org.apache.commons.codec.binary.StringUtils;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.integration.pac4j.authentication.handler.support.UsernamePasswordWrapperAuthenticationHandler;
import org.apereo.cas.services.ServicesManager;
import org.pac4j.core.credentials.UsernamePasswordCredentials;
import org.pac4j.core.credentials.authenticator.Authenticator;
import org.pac4j.core.credentials.password.PasswordEncoder;
import org.pac4j.mongo.credentials.authenticator.MongoAuthenticator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

/**
 * An authentication handler to verify credentials against a MongoDb instance.
 * @author Misagh Moayyed
 * @since 4.2.0
 */
public class MongoAuthenticationHandler extends UsernamePasswordWrapperAuthenticationHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(MongoAuthenticationHandler.class);
    
    private String collectionName;
    private String mongoHostUri;
    private String attributes;
    private String usernameAttribute;
    private String passwordAttribute;
    private MongoClientURI uri;
    private MongoClient client;
    
    private PasswordEncoder mongoPasswordEncoder = new NoOpPasswordEncoder();

    public MongoAuthenticationHandler(final String name, final ServicesManager servicesManager, final PrincipalFactory principalFactory,
                                      final String collectionName, final String mongoHostUri, final String attributes, final String usernameAttribute,
                                      final String passwordAttribute, final PasswordEncoder mongoPasswordEncoder) {
        super(name, servicesManager, principalFactory, null);
        this.collectionName = collectionName;
        this.mongoHostUri = mongoHostUri;
        this.attributes = attributes;
        this.usernameAttribute = usernameAttribute;
        this.passwordAttribute = passwordAttribute;
        this.mongoPasswordEncoder = mongoPasswordEncoder;
    }

    @PostConstruct
    private void createMongoClient() {
        uri = new MongoClientURI(this.mongoHostUri);
        client = new MongoClient(uri);
    }

    @PreDestroy
    private void cleanupResources() {
        client.close();
    }

    @Override
    protected Authenticator<UsernamePasswordCredentials> getAuthenticator(final Credential credential) {
        LOGGER.info("Connected to MongoDb instance @ {} using database [{}]",
                uri.getHosts(), uri.getDatabase());

        final MongoAuthenticator mongoAuthenticator = new MongoAuthenticator(client, this.attributes);
        mongoAuthenticator.setUsersCollection(this.collectionName);
        mongoAuthenticator.setUsersDatabase(uri.getDatabase());
        mongoAuthenticator.setUsernameAttribute(this.usernameAttribute);
        mongoAuthenticator.setPasswordAttribute(this.passwordAttribute);
        mongoAuthenticator.setPasswordEncoder(this.mongoPasswordEncoder);
        return mongoAuthenticator;
    }
    
    private static class NoOpPasswordEncoder implements PasswordEncoder {
        @Override
        public String encode(final String s) {
            LOGGER.debug("No password encoding shall take place by CAS");
            return s;
        }

        @Override
        public boolean matches(final String s, final String s1) {
            return StringUtils.equals(s, s1);
        }
    }
}
