/*
 * Copyright (c) 2024, WSO2 Inc. (http://www.wso2.com).
 *
 * WSO2 Inc. licenses this file to you 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.
 */

package org.wso2.carbon.is.migration.service.v710.migrator;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.carbon.identity.core.migrate.MigrationClientException;
import org.wso2.carbon.identity.governance.exceptions.notiification.NotificationTemplateManagerServerException;
import org.wso2.carbon.identity.governance.model.NotificationTemplate;
import org.wso2.carbon.identity.governance.service.notification.NotificationChannels;
import org.wso2.carbon.is.migration.service.Migrator;
import org.wso2.carbon.is.migration.service.v710.dao.NotificationDAO;
import org.wso2.carbon.is.migration.service.v710.util.NotificationTemplateUtils;
import org.wso2.carbon.is.migration.util.Utility;
import org.wso2.carbon.user.api.Tenant;

import java.sql.SQLTransientConnectionException;
import java.sql.SQLTransientException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import static org.wso2.carbon.base.MultitenantConstants.SUPER_TENANT_DOMAIN_NAME;
import static org.wso2.carbon.base.MultitenantConstants.SUPER_TENANT_ID;

/**
 * Migration implementation to cleanup system email template data.
 */
public class SystemTemplateMigrator extends Migrator {

    private static final Logger LOG = LoggerFactory.getLogger(SystemTemplateMigrator.class);
    private static final String WAIT_TIME = "waitTime";
    private static final long DEFAULT_WAIT_TIME_IN_MILLIS = 500;

    private final NotificationDAO notificationDAO = new NotificationDAO();

    private List<NotificationTemplate> defaultEmailNotificationTemplates;
    private List<NotificationTemplate> defaultSMSNotificationTemplates;
    private long waitTimeInMillis = DEFAULT_WAIT_TIME_IN_MILLIS;

    @Override
    public void dryRun() throws MigrationClientException {

        LOG.info("STM>> System template cleanup (dry run) started.");
        cleanupSystemTemplates(true);
    }

    @Override
    public void migrate() throws MigrationClientException {

        LOG.info("STM>> System template cleanup started.");
        cleanupSystemTemplates(false);
    }

    private void cleanupSystemTemplates(boolean isDryRun) throws MigrationClientException {

        Properties migrationProperties = getMigratorConfig().getParameters();
        if (migrationProperties.containsKey(WAIT_TIME)) {
            waitTimeInMillis = (int) migrationProperties.get(WAIT_TIME);
        }
        LOG.info("STM>> Parameters: Wait time {}ms.", waitTimeInMillis);

        defaultEmailNotificationTemplates = NotificationTemplateUtils.loadDefaultTemplatesFromFile(
                NotificationChannels.EMAIL_CHANNEL.getChannelType());
        defaultSMSNotificationTemplates = NotificationTemplateUtils.loadDefaultTemplatesFromFile(
                NotificationChannels.SMS_CHANNEL.getChannelType());
        LOG.info("STM>> Loaded {} & {} system EMAIL & SMS default templates.", defaultEmailNotificationTemplates.size(),
                defaultSMSNotificationTemplates.size());

        int successTenantCount = 0;
        int errorTenantCount = 0;
        int inactiveTenantCount = 0;

        Set<Tenant> tenants = getTenants();

        int totalTenantCount = tenants.size();
        LOG.info("STM>> Found {} tenants in the system.", totalTenantCount + 1); // Including super tenant.

        try {
            // Migrate super tenant.
            cleanupSystemTemplatesForTenant(isDryRun, false, SUPER_TENANT_ID, SUPER_TENANT_DOMAIN_NAME);
            successTenantCount++;

            // Migrate other tenants.
            boolean ignoreForInactiveTenants = isIgnoreForInactiveTenants();
            if (ignoreForInactiveTenants) {
                tenants = Utility.getActiveTenants(tenants);
                inactiveTenantCount = totalTenantCount - tenants.size();
                LOG.info("STM>> Excluding {} inactive tenants from the migration.", inactiveTenantCount);
            }
            totalTenantCount = tenants.size() + 1; // Including super tenant.
            LOG.info("STM>> Selected {} tenants for the migration.", totalTenantCount);

            List<Tenant> tenantList = new ArrayList<>(tenants);
            tenantList.sort(Comparator.comparingInt(Tenant::getId));

            int i = 0;
            for (Tenant tenant : tenantList) {
                i++;
                int tenantId = tenant.getId();
                String tenantDomain = tenant.getDomain();

                LOG.info("STM>> Starting migration for {} of {} tenant.", i, totalTenantCount);
                LOG.info("STM>> Migrating tenant: {} with tid: {}.", tenantDomain, tenantId);

                try {
                    cleanupSystemTemplatesForTenant(isDryRun, false, tenantId, tenantDomain);
                    successTenantCount++;
                } catch (MigrationClientException e) {
                    if (!isContinueOnError()) {
                        throw new NotificationTemplateManagerServerException(e.getMessage(), e);
                    }
                    errorTenantCount++;
                    LOG.error("STM>> Error while migrating notification templates in tenant {}.", tenantDomain, e);
                }
                LOG.info("STM>> Notification template migration successfully completed for tenant {}.", tenantDomain);
            }
        } catch (NotificationTemplateManagerServerException e) {
            errorTenantCount++;
            LOG.error("STM>> Error while migrating notification templates.", e);
        }

        if (errorTenantCount == 0) {
            LOG.info("STM>> Notification templates residual data migration successfully completed.");
        } else {
            LOG.info("STM>> Notification templates residual data migration completed with errors in {} tenants.",
                    errorTenantCount);
        }
        LOG.info("STM>> Successfully cleaned up: {} of {} tenants.", successTenantCount, totalTenantCount);
        LOG.info("STM>> Found issues with:  {} of {} tenants.", errorTenantCount, totalTenantCount);
        LOG.info("STM>> Skipped {} inactive tenants.", inactiveTenantCount);
    }

    private void cleanupSystemTemplatesForTenant(boolean isDryRun, boolean isRetry, int tenantId, String tenantDomain)
            throws MigrationClientException {

        try {
            try {
                notificationDAO.deleteSystemDefaultNotificationTemplates(defaultEmailNotificationTemplates,
                        NotificationChannels.EMAIL_CHANNEL.getChannelType(), isDryRun, tenantId);
                notificationDAO.deleteSystemDefaultNotificationTemplates(defaultSMSNotificationTemplates,
                        NotificationChannels.SMS_CHANNEL.getChannelType(), isDryRun, tenantId);
                LOG.info("STM>> System default templates successfully cleanup up in database for tenant {}.",
                        tenantDomain);
            } finally {
                if (waitTimeInMillis > 0) {
                    try {
                        LOG.info("STM>> Waiting {}ms to cool down...", waitTimeInMillis);
                        Thread.sleep(waitTimeInMillis);
                    } catch (InterruptedException e) {
                        LOG.error("STM>> Error while waiting for the next batch.", e);
                    }
                }
            }
        } catch (NotificationTemplateManagerServerException e) {
            // This is ideally needed to be handled by each respective components.
            // Since carbon components doesn't handle transient exceptions across the board, we need to handle it here.
            if (!isRetry && isTransientException(e)) {
                LOG.error("STM>> Transient error occurred while migrating notification templates in tenant {}. ",
                        tenantDomain, e);
                try {
                    LOG.info("STM>> Retrying after waiting for 2 minutes.");
                    Thread.sleep(120 * 1000);
                } catch (InterruptedException ex) {
                    LOG.error("STM>> Retrying without waiting.", e);
                }
                cleanupSystemTemplatesForTenant(isDryRun, true, tenantId, tenantDomain);
            } else {
                throw new MigrationClientException("Error while migrating tenant: " + tenantDomain, e);
            }
        }
    }

    private boolean isTransientException(Exception e) {

        Throwable rootCause = ExceptionUtils.getRootCause(e);
        return rootCause instanceof SQLTransientException || rootCause instanceof SQLTransientConnectionException ||
                (rootCause != null && rootCause.getMessage() != null &&
                        rootCause.getMessage().contains("Connection reset by peer"));
    }
}
