/**
 * This file is part of the Meeds project (https://meeds.io/).
 *
 * Copyright (C) 2020 - 2025 Meeds Association contact@meeds.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.exoplatform.services.organization.idm;

import java.io.InputStream;
import java.net.URL;
import java.security.PrivilegedAction;
import java.util.List;
import java.util.Properties;

import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.picketlink.idm.api.Attribute;
import org.picketlink.idm.api.AttributesManager;
import org.picketlink.idm.api.IdentitySession;
import org.picketlink.idm.api.IdentitySessionFactory;
import org.picketlink.idm.api.SortOrder;
import org.picketlink.idm.api.Transaction;
import org.picketlink.idm.api.User;
import org.picketlink.idm.api.cfg.IdentityConfiguration;
import org.picketlink.idm.api.query.UserQuery;
import org.picketlink.idm.api.query.UserQueryBuilder;
import org.picketlink.idm.common.exception.IdentityConfigurationException;
import org.picketlink.idm.impl.api.SimpleAttribute;
import org.picketlink.idm.impl.configuration.IdentityConfigurationImpl;
import org.picketlink.idm.impl.configuration.jaxb2.JAXB2IdentityConfiguration;
import org.picketlink.idm.spi.configuration.metadata.IdentityConfigurationMetaData;

public class DisabledUserMigrationScript {

    private String realmName;

    private IdentitySessionFactory identitySessionFactory;

    private SessionFactory sessionFactory_;

    private int batch;

    private static final Log log = ExoLogger.getExoLogger(DisabledUserMigrationScript.class);

    public DisabledUserMigrationScript(Properties config) throws Exception {
        // setup Hibernate
        setupHibernate(config);
        // setup PicketLink
        setupPicketlink(config);

        String b = config.getProperty("batch");
        this.batch = Integer.parseInt(b == null ? "100" : b);
    }

    public void enableAll(int first) throws Exception {
        final long startTime = System.currentTimeMillis();
        startTransaction();
        final int size = getIdentitySession().getPersistenceManager().getUserCount();

        log.info("Starting enable for {} users", size - first);
        int length = batch > size ? size : batch;

        while (length > 0) {
            try {
                log.info("enable for user from {} to {}", first, (first + length));
                startTransaction();
                UserQueryBuilder qb = getIdentitySession().createUserQueryBuilder();
                List<User> users = load(qb, first, length);
                for (User user : users) {
                    setEnabled(user.getId(), true);
                }
                getIdentitySession().save();
                endTransaction();
            } catch (Exception e) {
                log.info("fail to migrate for users from index: {}", first);
                recoverFromIDMError(e);
                break;
            }
            first = first + batch;
            length = batch + first > size ? size - first : batch;
        }

        log.info("Finish enable all users in : {}ms", (System.currentTimeMillis() - startTime));
    }

    private List<org.picketlink.idm.api.User> load(UserQueryBuilder qb, int index, int length) throws Exception {
        qb.sort(SortOrder.ASCENDING).page(index, length);
        UserQuery query = qb.createQuery();
        return getIdentitySession().list(query);
    }

    public void setEnabled(String userName, boolean enabled) throws Exception {
        IdentitySession session = getIdentitySession();
        AttributesManager am = session.getAttributesManager();
        if (enabled){
            am.removeAttributes(userName, new String[]{EntityMapperUtils.USER_ENABLED});
        } else {
            Attribute[] attrs = new Attribute[] { new SimpleAttribute(EntityMapperUtils.USER_ENABLED, String.valueOf(enabled)) };
            am.updateAttributes(userName, attrs);
        }
    }

    public void startTransaction() throws Exception {
        if (!getIdentitySession().getTransaction().isActive()) {
            getIdentitySession().beginTransaction();
        }
    }

    public void endTransaction() throws Exception {
        if (getIdentitySession().getTransaction().isActive()) {
            getIdentitySession().getTransaction().commit();
        }
    }

    public void recoverFromIDMError(Exception e) {
        log.error(e);
        try {
            // We need to restart Hibernate transaction if it's available. First rollback old one and then start new one
            Transaction idmTransaction = getIdentitySession().getTransaction();
            if (idmTransaction.isActive()) {
                idmTransaction.rollback();
                log.info("IDM error recovery finished. Old transaction has been rolled-back");
            }
        } catch (Exception e1) {
            log.warn("Error during recovery of old error", e1);
        }
    }

    private void setupHibernate(Properties config) throws Exception {
        final Configuration conf_ = new Configuration();

        conf_.setProperty("hibernate.connection.driver_class", config.getProperty("hibernate.connection.driver_class"));
        conf_.setProperty("hibernate.connection.url", config.getProperty("hibernate.connection.url"));
        conf_.setProperty("hibernate.connection.username", config.getProperty("hibernate.connection.username"));
        conf_.setProperty("hibernate.connection.password", config.getProperty("hibernate.connection.password"));

        String config_path = config.getProperty("hibernate.config_path");
        URL url = Thread.currentThread().getContextClassLoader().getResource(config_path);
        if (url == null) {
            log.error("hibernate config file not found: {}", config_path);
        } else {
            log.info("adding hibernate config file {}", config_path);
            conf_.addURL(url);
        }

        sessionFactory_ = conf_.configure().buildSessionFactory();
    }

    private void setupPicketlink(Properties config) throws Exception {
        String pkConfig = config.getProperty("picketlink.config_file_path");
        URL pk_config_url = Thread.currentThread().getContextClassLoader().getResource(pkConfig);
        if (pk_config_url == null) {
            throw new IllegalStateException("Cannot fine resource: " + pkConfig);
        }
        this.realmName = config.getProperty("picketlink.realmName", "idm_realm");

        IdentityConfigurationMetaData configMD = JAXB2IdentityConfiguration.createConfigurationMetaData(pk_config_url.openStream());
        IdentityConfiguration identityConfiguration = new IdentityConfigurationImpl().configure(configMD);
        identityConfiguration.getIdentityConfigurationRegistry().register(sessionFactory_, "hibernateSessionFactory");

        if (identitySessionFactory == null) {
            try {
                identitySessionFactory = identityConfiguration.buildIdentitySessionFactory();
            } catch (IdentityConfigurationException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public IdentitySession getIdentitySession() throws Exception {
        return identitySessionFactory.getCurrentIdentitySession(realmName);
    }

    public static void main(String[] args) throws Exception {
        Properties config = new Properties();

        String config_path = args.length > 0 ? args[0] : null;
        if (config_path != null) {
            URL config_url = Thread.currentThread().getContextClassLoader().getResource(config_path);
            if (config_url == null) {
                log.error("config file is not found: {}", config_path);
            } else {
                log.info("using config file {}", config_url);
                config.load(config_url.openStream());
            }
        } else {
            log.info("using default config file");
            InputStream defInput = Thread.currentThread().getContextClassLoader()
                    .getResourceAsStream("configuration.properties");
            config.load(defInput);
        }

        for (Object key : System.getProperties().keySet()) {
            config.setProperty((String) key, System.getProperty((String) key));
        }

        int first = Integer.parseInt(config.getProperty("enable_user_from", "0"));
        DisabledUserMigrationScript service = new DisabledUserMigrationScript(config);
        service.enableAll(first);
        service.sessionFactory_.close();
    }
}
