/*
 * org.nrg.xnat.security.DisableInactiveUsersJob
 * XNAT http://www.xnat.org
 * Copyright (c) 2014, Washington University School of Medicine
 * All Rights Reserved
 *
 * Released under the Simplified BSD.
 *
 * Last modified 11/4/13 9:51 AM
 */
package org.nrg.xnat.security;

import org.apache.commons.lang3.time.DateUtils;
import org.nrg.xdat.security.helpers.Users;
import org.nrg.xdat.turbine.utils.AdminUtils;
import org.nrg.xdat.turbine.utils.TurbineUtils;
import org.nrg.xft.XFTTable;
import org.nrg.xft.event.EventUtils;
import org.nrg.xft.exception.DBPoolException;
import org.nrg.xft.exception.InvalidPermissionException;
import org.nrg.xft.security.UserI;
import org.nrg.xnat.utils.XnatUserProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.SQLException;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

public class DisableInactiveUsers implements Runnable {

    public DisableInactiveUsers(final XnatUserProvider userProvider, final int inactivityBeforeLockout, final int lockoutDuration) {
        _userProvider = userProvider;
        _inactivityBeforeLockout = inactivityBeforeLockout;
        _lockoutDuration = lockoutDuration;
    }

    /**
     * Finds user accounts that have not been validated or authenticated within the indicated time frame
     * and disables them.
     */
    @Override
    public void run() {
        try {
            final UserI adminUser = _userProvider.get();

            final XFTTable table = XFTTable.Execute("SELECT xdat_user.login FROM xdat_user INNER JOIN " +
                                                    "(" +
                                                    "SELECT y.login, last_login, activation_date FROM xdat_user_meta_data INNER JOIN " +
                                                    "(" +
                                                    "SELECT xdat_user.login, xdat_user.xdat_user_id, MAX(xdat_user_login.login_date) AS last_login FROM xdat_user_login RIGHT JOIN xdat_user ON xdat_user_login.user_xdat_user_id=xdat_user.xdat_user_id GROUP BY xdat_user.login,xdat_user.xdat_user_id" +
                                                    ") y " + //get last login times for each user
                                                    "ON y.xdat_user_id=xdat_user_meta_data.meta_data_id AND y.login NOT IN (SELECT username FROM xhbm_user_role WHERE role='Administrator') AND y.xdat_user_id NOT IN (SELECT xdat_user_xdat_user_id FROM xdat_r_xdat_role_type_assign_xdat_user WHERE xdat_r_xdat_role_type_assign_xdat_user.xdat_role_type_role_name = 'Administrator')" +
                                                    ") x " + //get dates that each non-admin user was created
                                                    "ON x.login=xdat_user.login AND ((x.activation_date<(now()- INTERVAL '" + _inactivityBeforeLockout + " seconds')) AND ((x.last_login IS NULL) OR x.last_login<(now()- INTERVAL '" + _inactivityBeforeLockout + " seconds'))) AND xdat_user.enabled=1", null, null);

            table.resetRowCursor();
            while (table.hasMoreRows()) {
                final Object[] row = table.nextRow();

                final String username = (String) row[0];
                try {
                    final UserI u = Users.getUser(username);

                    // Fixes XNAT-2407. Only disable user if they have not been recently modified (enabled).
                    // Also do not disable the guest user.
                    if (!hasUserBeenModified(u, _inactivityBeforeLockout) && !username.equals("guest")) {
                        u.setEnabled("0");
                        u.setVerified("0");
                        Users.save(u, u, false, EventUtils.newEventInstance(EventUtils.CATEGORY.SIDE_ADMIN, EventUtils.TYPE.PROCESS, "Disabled due to inactivity"));

                        String expiration = TurbineUtils.getDateTimeFormatter().format(DateUtils.addMilliseconds(GregorianCalendar.getInstance().getTime(), _lockoutDuration));
                        System.out.println("Locked out " + u.getLogin() + " user account until " + expiration);
                        AdminUtils.sendAdminEmail(u.getLogin() + " account disabled due to inactivity.", "User " + u.getLogin() + " has been automatically disabled due to inactivity.");
                    }
                } catch (InvalidPermissionException e) {
                    if (e.getMessage().contains("wrk:workflowData")) {
                        logger.warn("An invalid permission exception was encountered while attempting to disable the user {} with provided user {}. This probably indicates that the system is still initializing. If it occurs frequently after the system has started, there may be an issue with your database schema or data.");
                    } else {
                        logger.error("An unexpected invalid permission exception occurred", e);
                    }
                } catch (Exception e) {
                    logger.error("", e);
                }
            }
        } catch (SQLException e) {
            logger.error("An error occurred querying the database: [" + e.getErrorCode() + "] " + e.getSQLState(), e);
        } catch (DBPoolException e) {
            logger.error("A database connection or pooling error occurred.", e);
        }
    }

    /**
     * Function determines if the user has been modified in the past amount of seconds.
     * Fixes XNAT-2407. This keeps the job from disabling a user if the admin has just enabled (modified) them.
     *
     * @param u       - the user we are interested in.
     * @param seconds - Has the user been modified in the past amount of seconds.
     *
     * @return true if the user has been modified / otherwise false.
     */
    private boolean hasUserBeenModified(final UserI u, final int seconds) {

        // Subtract seconds from today's date.
        final Calendar c = Calendar.getInstance();
        c.add(Calendar.SECOND, -seconds);

        // If the time is before the last modified date, the user has been modified.
        final Date lastModified = u.getLastModified();
        return lastModified != null && (c.getTime().before(lastModified));
    }

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

    private final XnatUserProvider _userProvider;
    private final int              _inactivityBeforeLockout;
    private final int              _lockoutDuration;
}