/*
 * This file is part of Alpine.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 * Copyright (c) Steve Springett. All Rights Reserved.
 */
package alpine.tasks;

import alpine.auth.LdapConnectionWrapper;
import alpine.event.LdapSyncEvent;
import alpine.event.framework.Event;
import alpine.event.framework.Subscriber;
import alpine.logging.Logger;
import alpine.model.LdapUser;
import alpine.persistence.AlpineQueryManager;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchResult;
import java.util.ArrayList;
import java.util.List;

/**
 * A task to synchronize LDAP users. This should be added to a concrete class that
 * extends {@link AlpineTaskScheduler}.
 *
 * @author Steve Springett
 * @since 1.0.0
 */
public class LdapSyncTask implements Subscriber {

    private static final Logger LOGGER = Logger.getLogger(LdapSyncTask.class);

    @Override
    public void inform(final Event e) {

        if (!LdapConnectionWrapper.LDAP_CONFIGURED) {
            return;
        }

        if (e instanceof LdapSyncEvent) {
            LOGGER.info("Starting LDAP synchronization task");
            final LdapSyncEvent event = (LdapSyncEvent) e;
            final LdapConnectionWrapper ldap = new LdapConnectionWrapper();
            DirContext ctx = null;
            try (AlpineQueryManager qm = new AlpineQueryManager()) {
                ctx = ldap.createDirContext();
                if (event.getUsername() == null) {
                    // If username was null, we are going to sync all users
                    final List<LdapUser> users = qm.getLdapUsers();
                    for (final LdapUser user: users) {
                        sync(ctx, qm, ldap, user);
                    }
                } else {
                    // If username was specified, we will only sync the one
                    final LdapUser user = qm.getLdapUser(event.getUsername());
                    if (user != null) {
                        sync(ctx, qm, ldap, user);
                    }
                }
            } catch (NamingException ex) {
                LOGGER.error("Error occurred during LDAP synchronization", ex);
            } finally {
                ldap.closeQuietly(ctx);
                LOGGER.info("LDAP synchronization complete");
            }
        }
    }

    /**
     * Performs the actual sync of the specified user.
     * @param ctx a DirContext
     * @param qm the AlpineQueryManager to use
     * @param ldap the LdapConnectionWrapper to use
     * @param user the LdapUser instance to sync
     * @throws NamingException when a problem with the connection with the directory
     */
    private void sync(final DirContext ctx, final AlpineQueryManager qm, final LdapConnectionWrapper ldap,
                      LdapUser user) throws NamingException {
        LOGGER.debug("Syncing: " + user.getUsername());
        final SearchResult result = ldap.searchForSingleUsername(ctx, user.getUsername());
        if (result != null) {
            user.setDN(result.getNameInNamespace());
            user.setEmail(ldap.getAttribute(result, LdapConnectionWrapper.ATTRIBUTE_MAIL));
            user = qm.updateLdapUser(user);
            // Dynamically assign team membership (if enabled)
            if (LdapConnectionWrapper.TEAM_SYNCHRONIZATION) {
                final List<String> groupDNs = ldap.getGroups(ctx, user);
                qm.synchronizeTeamMembership(user, groupDNs);
            }
        } else {
            // This is an invalid user - a username that exists in the database that does not exist in LDAP
            user.setDN("INVALID");
            user.setEmail(null);
            user = qm.updateLdapUser(user);
            if (LdapConnectionWrapper.TEAM_SYNCHRONIZATION) {
                qm.synchronizeTeamMembership(user, new ArrayList<>());
            }
        }
    }

}