/*
 * 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 com.google.gson.Gson;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.email.mgt.constants.I18nMgtConstants;
import org.wso2.carbon.identity.base.IdentityRuntimeException;
import org.wso2.carbon.identity.core.migrate.MigrationClientException;
import org.wso2.carbon.identity.core.persistence.registry.RegistryResourceMgtService;
import org.wso2.carbon.identity.core.persistence.registry.RegistryResourceMgtServiceImpl;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
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.model.ScenarioTemplatesDTO;
import org.wso2.carbon.is.migration.util.Utility;
import org.wso2.carbon.registry.core.Collection;
import org.wso2.carbon.registry.core.Resource;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
import org.wso2.carbon.user.api.Tenant;
import org.wso2.carbon.utils.CarbonUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.sql.SQLTransientConnectionException;
import java.sql.SQLTransientException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import static org.wso2.carbon.base.MultitenantConstants.SUPER_TENANT_DOMAIN_NAME;
import static org.wso2.carbon.base.MultitenantConstants.SUPER_TENANT_ID;
import static org.wso2.carbon.is.migration.service.v710.constants.NotificationRegistryConstants.APP_TEMPLATE_PATH;
import static org.wso2.carbon.is.migration.service.v710.constants.NotificationRegistryConstants.CHARSET_CONSTANT;
import static org.wso2.carbon.is.migration.service.v710.constants.NotificationRegistryConstants.CHARSET_UTF_8;
import static org.wso2.carbon.is.migration.service.v710.constants.NotificationRegistryConstants.EMAIL_TEMPLATE_PATH;
import static org.wso2.carbon.is.migration.service.v710.constants.NotificationRegistryConstants.SMS_TEMPLATE_PATH;
import static org.wso2.carbon.is.migration.service.v710.constants.NotificationRegistryConstants.TEMPLATE_CONTENT_TYPE;
import static org.wso2.carbon.is.migration.service.v710.constants.NotificationRegistryConstants.TEMPLATE_DISPLAY_NAME;
import static org.wso2.carbon.is.migration.service.v710.constants.NotificationRegistryConstants.TEMPLATE_LOCALE;
import static org.wso2.carbon.is.migration.service.v710.constants.NotificationRegistryConstants.TEMPLATE_TYPE;
import static org.wso2.carbon.is.migration.service.v710.constants.NotificationRegistryConstants.TEMPLATE_TYPE_DISPLAY_NAME;

/**
 * Migration implementation to migrate residual email template data.
 */
public class NotificationTemplateMigrator extends Migrator {

    private static final Logger LOG = LoggerFactory.getLogger(NotificationTemplateMigrator.class);
    private static final String WAIT_TIME = "waitTime";
    private static final String ROLLBACK_ALLOWED = "rollbackAllowed";
    private static final String HAS_MIGRATED = "hasMigrated";
    private static final long DEFAULT_WAIT_TIME_IN_MILLIS = 500;
    List<NotificationTemplate> systemDefaultNotificationTemplates;

    private final NotificationDAO notificationDAO = new NotificationDAO();
    private final RegistryResourceMgtService resourceMgtService = new RegistryResourceMgtServiceImpl();

    long waitTimeInMillis = DEFAULT_WAIT_TIME_IN_MILLIS;
    boolean isRollbackAllowed = false;

    @Override
    public void dryRun() throws MigrationClientException {

        LOG.info("RDM-NT-DR>> Residual data migration (dry run) of notification templates started.");
        migrateNotificationTemplates(true);
    }

    @Override
    public void migrate() throws MigrationClientException {

        LOG.info("RDM-NT>> Residual data migration of notification templates started.");
        migrateNotificationTemplates(false);
    }

    private void migrateNotificationTemplates(boolean isDryRun) throws MigrationClientException {

        Properties migrationProperties = getMigratorConfig().getParameters();

        if (migrationProperties.containsKey(WAIT_TIME)) {
            waitTimeInMillis = (int) migrationProperties.get(WAIT_TIME);
        }
        LOG.info("RDM-NT>> Parameters: Wait time {}ms.", waitTimeInMillis);

        if (migrationProperties.containsKey(ROLLBACK_ALLOWED)) {
            String rollback = (String) migrationProperties.get(ROLLBACK_ALLOWED);

            if (StringUtils.isNotBlank(rollback) && Boolean.parseBoolean(rollback)) {
                isRollbackAllowed = true;
            }
        }
        LOG.info("RDM-NT>> Parameters: isRollbackAllowed: {}.", isRollbackAllowed);

        systemDefaultNotificationTemplates = loadSystemDefaultNotificationTemplates();
        LOG.info("RDM-NT>> Loaded {} system default templates.", systemDefaultNotificationTemplates.size());

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

        Set<Tenant> tenants = getTenants();
        int totalTenantCount = tenants.size();
        LOG.info("RDM-NT>> Found {} tenants in the system.", totalTenantCount + 1); // Including super tenant.

        try {
            // Migrate super tenant.
            migrateForTenant(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("RDM-NT>> Excluding {} inactive tenants from the migration.", inactiveTenantCount);
            }
            totalTenantCount = tenants.size() + 1; // Including super tenant.
            LOG.info("RDM-NT>> Selected {} tenants for the migration.", totalTenantCount);

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

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

                try {
                    PrivilegedCarbonContext.startTenantFlow();
                    PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext();
                    carbonContext.setTenantId(tenantId);
                    carbonContext.setTenantDomain(tenantDomain);
                    migrateForTenant(isDryRun, false, tenantId, tenantDomain);
                    successTenantCount++;
                } catch (MigrationClientException e) {
                    if (!isContinueOnError()) {
                        throw new NotificationTemplateManagerServerException(e.getMessage(), e);
                    }
                    errorTenantCount++;
                    LOG.error("RDM-NT>> Error while migrating notification templates in tenant {}.", tenantDomain, e);
                } finally {
                    PrivilegedCarbonContext.endTenantFlow();
                }
                LOG.info("RDM-NT>> Notification template migration successfully completed for tenant {}.",
                        tenantDomain);
            }
        } catch (NotificationTemplateManagerServerException e) {
            errorTenantCount++;
            LOG.error("RDM-NT>> Error while migrating notification templates.", e);
        }

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

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

        try {
            if (tenantId != SUPER_TENANT_ID) {
                IdentityTenantUtil.getTenantRegistryLoader().loadTenantRegistry(tenantId);
            }
            migrateNotificationTemplates(isDryRun, tenantDomain);
        } catch (RegistryException | 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("RDM-NT>> Transient error occurred while migrating notification templates in tenant {}. ",
                        tenantDomain, e);
                try {
                    LOG.info("RDM-NT>> Retrying after waiting for 2s.");
                    wait(2000);
                } catch (InterruptedException ex) {
                    LOG.error("RDM-NT>> Retrying without waiting.", e);
                }
                migrateForTenant(isDryRun, true, tenantId, tenantDomain);
            } else {
                throw new MigrationClientException("Error while migrating tenant: " + tenantDomain, e);
            }
        }
    }

    private static 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"));
    }

    private void migrateNotificationTemplates(boolean isDryRun, String tenantDomain)
            throws NotificationTemplateManagerServerException {

        migrateNotificationTemplatesForChannel(NotificationChannels.EMAIL_CHANNEL.getChannelType(), isDryRun,
                tenantDomain);
        migrateNotificationTemplatesForChannel(NotificationChannels.SMS_CHANNEL.getChannelType(), isDryRun,
                tenantDomain);
    }

    private void migrateNotificationTemplatesForChannel(String channel, boolean isDryRun, String tenantDomain)
            throws NotificationTemplateManagerServerException {

        String regPathForChannel = EMAIL_TEMPLATE_PATH;
        if (NotificationChannels.SMS_CHANNEL.getChannelType().equals(channel)) {
            regPathForChannel = SMS_TEMPLATE_PATH;
        }

        Resource resource;
        try {
            resource = resourceMgtService.getIdentityResource(regPathForChannel, tenantDomain);
            if (resource == null) {
                LOG.info("RDM-NT>> No {} templates found in tenant {}.", channel, tenantDomain);
                return;
            }

            if (resource.getProperty(HAS_MIGRATED) != null && resource.getProperty(HAS_MIGRATED).equals(Boolean.TRUE)) {
                LOG.info("RDM-NT>> {} templates already migrated for tenant {}.", channel, tenantDomain);
                return;
            }
        } catch (IdentityRuntimeException e) {
            String msg = String.format("Error while checking the existence of %s templates in tenant %s.", channel,
                    tenantDomain);
            throw new NotificationTemplateManagerServerException(msg, e);
        }

        LOG.info("RDM-NT>> Listing {} notification templates in tenant {}.", channel, tenantDomain);
        Map<String, ScenarioTemplatesDTO> notificationTemplates =
                getTemplatesFromRegistry(regPathForChannel, channel, tenantDomain);

        if (notificationTemplates.isEmpty()) {
            LOG.info("RDM-NT>> No {} notification scenarios found in tenant {}.", channel, tenantDomain);
            return;
        }
        LOG.info("RDM-NT>> Found {} notification scenarios in Registry in tenant {}.", notificationTemplates.size(),
                tenantDomain);

        Map<String, ScenarioTemplatesDTO> filteredNotificationTemplates =
                excludeDefaultTemplates(notificationTemplates, tenantDomain);
        LOG.info("RDM-NT>> Found {} notification scenarios after excluding system default templates in tenant {}.",
                filteredNotificationTemplates.size(), tenantDomain);

        try {
            int tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId();
            if (!isRollbackAllowed) {
                notificationDAO.deleteSystemDefaultNotificationTemplates(systemDefaultNotificationTemplates,
                        channel, isDryRun, tenantId);
                LOG.info("RDM-NT>> System default {} templates successfully cleanup up in database for tenant {}.",
                        channel, tenantDomain);
            }
            List<String> scenarioList = new ArrayList<>(filteredNotificationTemplates.keySet());
            notificationDAO.migrateNotificationScenarios(scenarioList, channel, isDryRun, tenantId);
            LOG.info("RDM-NT>> notification {} scenarios successfully migrated in tenant {}.", channel, tenantDomain);

            List<NotificationTemplate> orgTemplates = new ArrayList<>();
            for (ScenarioTemplatesDTO scenarioTemplatesDTO : filteredNotificationTemplates.values()) {
                orgTemplates.addAll(scenarioTemplatesDTO.getOrgTemplates());
            }
            notificationDAO.migrateOrgNotificationTemplates(orgTemplates, channel, isDryRun, tenantId);
            LOG.info("RDM-NT>> Org {} templates successfully migrated in tenant {}.", channel, tenantDomain);

            Map<String, List<NotificationTemplate>> appTemplates = new HashMap<>();
            for (ScenarioTemplatesDTO scenarioTemplatesDTO : filteredNotificationTemplates.values()) {
                appTemplates.putAll(scenarioTemplatesDTO.getAppTemplates());
            }
            notificationDAO.migrateAppNotificationTemplates(appTemplates, channel, isDryRun, tenantId);
            LOG.info("RDM-NT>> App {} templates successfully migrated in tenant {}.", channel, tenantDomain);

            try {
                LOG.info("RDM-NT>> Cleaning {} templates in registry in tenant {}.", channel, tenantDomain);
                if (isDryRun) {
                    LOG.info("RDM-NT>> {} templates in registry in tenant {} deletion will be skipped due to dry run.",
                            channel, tenantDomain);
                } else if (isRollbackAllowed) {
                    resource.addProperty(HAS_MIGRATED, Boolean.TRUE.toString());
                    LOG.info(
                            "RDM-NT>> {} templates in registry in tenant {} deletion will be skipped allowing room " +
                                    "to rollback.", channel, tenantDomain);
                } else {
                    resourceMgtService.deleteIdentityResource(regPathForChannel, tenantDomain);
                    LOG.info("RDM-NT>> {} templates in registry successfully deleted for tenant {}.", channel,
                            tenantDomain);
                }
                LOG.info("RDM-NT>> {} template migration in tenant {} successfully completed.", channel, tenantDomain);
            } catch (IdentityRuntimeException e) {
                String msg = String.format("Error while cleaning %s templates in tenant %s.", channel, tenantDomain);
                throw new NotificationTemplateManagerServerException(msg, e);
            }
        } finally {
            if (waitTimeInMillis > 0) {
                try {
                    LOG.info("RDM-NT>> Waiting {}ms to cool down...", waitTimeInMillis);
                    Thread.sleep(waitTimeInMillis);
                } catch (InterruptedException e) {
                    LOG.error("RDM-NT>> Error while waiting for the next batch.", e);
                }
            }
        }
    }

    private Map<String, ScenarioTemplatesDTO> excludeDefaultTemplates(
            Map<String, ScenarioTemplatesDTO> notificationTemplates, String tenantDomain) {

        Map<String, ScenarioTemplatesDTO> filteredNotificationTemplates = new HashMap<>();
        for (Map.Entry<String, ScenarioTemplatesDTO> entry : notificationTemplates.entrySet()) {
            ScenarioTemplatesDTO scenarioTemplatesDTO = entry.getValue();
            List<NotificationTemplate> orgTemplates = new ArrayList<>();
            for (NotificationTemplate orgTemplate : scenarioTemplatesDTO.getOrgTemplates()) {
                if (!systemDefaultNotificationTemplates.contains(orgTemplate)) {
                    orgTemplates.add(orgTemplate);
                }
            }
            Map<String, List<NotificationTemplate>> appTemplates = new HashMap<>();
            for (Map.Entry<String, List<NotificationTemplate>> appEntry : scenarioTemplatesDTO.getAppTemplates().entrySet()) {
                List<NotificationTemplate> appTemplateList = new ArrayList<>();
                for (NotificationTemplate appTemplate : appEntry.getValue()) {
                    if (systemDefaultNotificationTemplates.contains(appTemplate)) {
                        String message = String.format(
                                "RDM-NT-DR>> App template: %s for application: %s in tenant: %s has the same " +
                                        "content as the system default template of the same type.",
                                appTemplate.getType(), appEntry.getKey(), tenantDomain);
                        LOG.warn(message);
                    }
                    appTemplateList.add(appTemplate);
                }
                appTemplates.put(appEntry.getKey(), appTemplateList);
            }
            filteredNotificationTemplates.put(entry.getKey(), new ScenarioTemplatesDTO(entry.getKey(), orgTemplates, appTemplates));
        }
        return filteredNotificationTemplates;
    }

    private Map<String, ScenarioTemplatesDTO> getTemplatesFromRegistry(String regPathForChannel,
                                                                       String notificationChannel,
                                                                       String tenantDomain)
            throws NotificationTemplateManagerServerException {

        try {
            Resource regResourceForChannel = resourceMgtService.getIdentityResource(regPathForChannel, tenantDomain);
            if (!(regResourceForChannel instanceof Collection)) {
                String message = String.format(
                        "Invalid registry resource found while trying to list %s notification templates in tenant %s.",
                        notificationChannel, tenantDomain);
                throw new NotificationTemplateManagerServerException(message);
            }

            Collection regCollectionForChannel = (Collection) regResourceForChannel;
            Map<String, ScenarioTemplatesDTO> templates = new HashMap<>();
            for (String regPathForScenario : regCollectionForChannel.getChildren()) {
                Resource templateTypeResource = resourceMgtService.getIdentityResource(regPathForScenario, tenantDomain);
                String templateTypeDisplayName = templateTypeResource.getProperty(TEMPLATE_DISPLAY_NAME);
                ScenarioTemplatesDTO templatesForScenario =
                        getTemplatesForScenario(templateTypeResource, templateTypeDisplayName, notificationChannel,
                                tenantDomain);
                templates.put(templateTypeDisplayName, templatesForScenario);
            }
            return templates;
        } catch (RegistryException | IdentityRuntimeException e) {
            String errorMessage =
                    String.format("Error while listing %s notification templates in tenant %s.", notificationChannel,
                            tenantDomain);
            throw new NotificationTemplateManagerServerException(errorMessage, e);
        }
    }

    private ScenarioTemplatesDTO getTemplatesForScenario(Resource templateTypeResource, String scenarioType,
                                                         String channel, String tenantDomain)
            throws RegistryException, NotificationTemplateManagerServerException {

        if (!(templateTypeResource instanceof Collection)) {
            String message = String.format(
                    "Invalid registry resource found while trying to list %s notification templates for scenario %s " +
                            "in tenant %s.", channel, scenarioType, tenantDomain);
            throw new NotificationTemplateManagerServerException(message);
        }

        Collection templateTypeCollection = (Collection) templateTypeResource;

        List<NotificationTemplate> orgTemplates =
                getTemplateLocaleList(templateTypeCollection, channel, true, tenantDomain);

        // handle app templates
        Map<String, List<NotificationTemplate>> appTemplates = new HashMap<>();
        for (String childPath : templateTypeCollection.getChildren()) {
            if (!childPath.endsWith(APP_TEMPLATE_PATH)) {
                continue;
            }

            Resource appBaseResource = resourceMgtService.getIdentityResource(childPath, tenantDomain);
            if (appBaseResource instanceof Collection) {
                Collection appBaseCollection = (Collection) appBaseResource;

                for (String appPath : appBaseCollection.getChildren()) {
                    Resource appUuidResource = resourceMgtService.getIdentityResource(appPath, tenantDomain);
                    if (appUuidResource instanceof Collection) {
                        Collection appUuidCollection = (Collection) appUuidResource;
                        List<NotificationTemplate> templateListForApp =
                                getTemplateLocaleList(appUuidCollection, channel, false, tenantDomain);
                        String appUuid = appPath.substring(appPath.lastIndexOf("/") + 1);
                        appTemplates.put(appUuid, templateListForApp);
                    }
                }
            }
        }

        return new ScenarioTemplatesDTO(scenarioType, orgTemplates, appTemplates);
    }

    private List<NotificationTemplate> getTemplateLocaleList(Collection templateCollection, String channel,
                                                             boolean isOrgTemplates, String tenantDomain)
            throws RegistryException, NotificationTemplateManagerServerException {

        List<NotificationTemplate> templateLocaleList = new ArrayList<>();
        for (String childPath : templateCollection.getChildren()) {
            Resource templateResource = resourceMgtService.getIdentityResource(childPath, tenantDomain);
            if (!(templateResource instanceof Collection)) {
                NotificationTemplate notificationTemplate = getNotificationTemplate(templateResource, channel);
                templateLocaleList.add(notificationTemplate);
            } else if (isOrgTemplates && childPath.endsWith(APP_TEMPLATE_PATH)) {
                // It's expected to have the apps template root collection as a child in org templates collection,
                continue;
            } else {
                String message = String.format(
                        "RDM-NT>> Invalid reg collection %s found when building %s template locale list in tenant %s.",
                        childPath, channel, tenantDomain);
                LOG.warn(message);
            }
        }
        return templateLocaleList;
    }

    private NotificationTemplate getNotificationTemplate(Resource templateResource, String notificationChannel)
            throws NotificationTemplateManagerServerException {

        NotificationTemplate notificationTemplate = new NotificationTemplate();

        // Get template meta properties.
        String displayName = templateResource.getProperty(TEMPLATE_TYPE_DISPLAY_NAME);
        String type = templateResource.getProperty(TEMPLATE_TYPE);
        String locale = templateResource.getProperty(TEMPLATE_LOCALE);

        if (NotificationChannels.EMAIL_CHANNEL.getChannelType().equals(notificationChannel)) {
            String contentType = templateResource.getProperty(TEMPLATE_CONTENT_TYPE);

            // Setting UTF-8 for all the email templates as it supports many languages and is widely adopted.
            // There is little to no value addition making the charset configurable.
            if (contentType != null && !contentType.toLowerCase().contains(CHARSET_CONSTANT)) {
                contentType = contentType + "; " + CHARSET_UTF_8;
            }
            notificationTemplate.setContentType(contentType);
        }

        notificationTemplate.setDisplayName(displayName);
        notificationTemplate.setType(type);
        notificationTemplate.setLocale(locale);
        // Set template subject, body and footer.
        setTemplateElements(templateResource, notificationTemplate, notificationChannel, displayName, locale);
        notificationTemplate.setNotificationChannel(notificationChannel);
        return notificationTemplate;
    }

    private void setTemplateElements(Resource templateResource, NotificationTemplate notificationTemplate,
                                     String notificationChannel, String displayName, String locale)
            throws NotificationTemplateManagerServerException {

        try {
            Object content = templateResource.getContent();
            if (content != null) {
                byte[] templateContentArray = (byte[]) content;
                String templateContent = new String(templateContentArray, StandardCharsets.UTF_8);
                String[] templateContentElements = new Gson().fromJson(templateContent, String[].class);

                // Validate template content.
                if (NotificationChannels.SMS_CHANNEL.getChannelType().equals(notificationChannel)) {
                    if (templateContentElements != null && templateContentElements.length == 1) {
                        notificationTemplate.setBody(templateContentElements[0]);
                    } else {
                        String message =
                                String.format("Invalid SMS template content found in the template: %s in locale: %s.",
                                        displayName, locale);
                        throw new NotificationTemplateManagerServerException(message);
                    }
                } else {
                    if (templateContentElements != null && templateContentElements.length == 3) {
                        notificationTemplate.setSubject(templateContentElements[0]);
                        notificationTemplate.setBody(templateContentElements[1]);
                        notificationTemplate.setFooter(templateContentElements[2]);
                    } else {
                        String message =
                                String.format("Invalid email template content found in the template: %s in locale: %s.",
                                        displayName, locale);
                        throw new NotificationTemplateManagerServerException(message);
                    }
                }
            } else {
                String message =
                        String.format("No content found in the template: %s in locale: %s.", displayName, locale);
                throw new NotificationTemplateManagerServerException(message);
            }
        } catch (RegistryException e) {
            String message = String.format("Error while getting the content of the template: %s in locale: %s.",
                    displayName, locale);
            throw new NotificationTemplateManagerServerException(message, e);
        }
    }

    private List<NotificationTemplate> loadSystemDefaultNotificationTemplates() throws MigrationClientException {

        List<NotificationTemplate> defaultTemplates = new ArrayList<>();
        defaultTemplates.addAll(loadDefaultTemplatesFromFile(NotificationChannels.EMAIL_CHANNEL.getChannelType()));
        defaultTemplates.addAll(loadDefaultTemplatesFromFile(NotificationChannels.SMS_CHANNEL.getChannelType()));
        return defaultTemplates;
    }

    /**
     * Get the template attributes of the notification template such as SUBJECT, BODY, EMAIL.
     *
     * @param templateElement OMElement
     * @return List of attributes in the notification template
     */
    private static Map<String, String> getNotificationTemplateContent(OMElement templateElement) {

        Map<String, String> notificationTemplateContent = new HashMap<>();
        Iterator it = templateElement.getChildElements();
        while (it.hasNext()) {
            OMElement element = (OMElement) it.next();
            String elementName = element.getLocalName();
            String elementText = element.getText();
            if (StringUtils.equalsIgnoreCase(I18nMgtConstants.TEMPLATE_SUBJECT, elementName)) {
                notificationTemplateContent.put(I18nMgtConstants.TEMPLATE_SUBJECT, elementText);
            } else if (StringUtils.equalsIgnoreCase(I18nMgtConstants.TEMPLATE_BODY, elementName)) {
                notificationTemplateContent.put(I18nMgtConstants.TEMPLATE_BODY, elementText);
            } else if (StringUtils.equalsIgnoreCase(I18nMgtConstants.TEMPLATE_FOOTER, elementName)) {
                notificationTemplateContent.put(I18nMgtConstants.TEMPLATE_FOOTER, elementText);
            }
        }
        return notificationTemplateContent;
    }

    /**
     * Loads the default templates from the file for the channel(EMAIL or SMS) and create list of Notification Template.
     *
     * @param notificationChannel Channel of the notification.
     * @return List of NotificationTemplate.
     */
    public List<NotificationTemplate> loadDefaultTemplatesFromFile(String notificationChannel) {

        String configFilePath = buildNotificationTemplateConfigPath(notificationChannel);
        File configFile = new File(configFilePath);
        if (!configFile.exists()) {
            LOG.error("Email Configuration File is not present at: " + configFilePath);
        }

        List<NotificationTemplate> defaultNotificationTemplates = new ArrayList<>();
        XMLStreamReader xmlStreamReader = null;

        try (InputStream inputStream = new FileInputStream(configFile)) {
            xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream);
            StAXOMBuilder builder = new StAXOMBuilder(xmlStreamReader);

            OMElement documentElement = builder.getDocumentElement();
            Iterator iterator = documentElement.getChildElements();
            while (iterator.hasNext()) {
                OMElement omElement = (OMElement) iterator.next();
                Map<String, String> templateContentMap = getNotificationTemplateContent(omElement);

                // Create notification template model with the template attributes.
                NotificationTemplate notificationTemplate = new NotificationTemplate();
                notificationTemplate.setType(omElement.getAttributeValue(new QName(I18nMgtConstants.TEMPLATE_TYPE)));
                notificationTemplate.setDisplayName(
                        omElement.getAttributeValue(new QName(I18nMgtConstants.TEMPLATE_TYPE_DISPLAY_NAME)));
                notificationTemplate
                        .setLocale(omElement.getAttributeValue(new QName(I18nMgtConstants.TEMPLATE_LOCALE)));
                notificationTemplate.setBody(templateContentMap.get(I18nMgtConstants.TEMPLATE_BODY));
                notificationTemplate.setNotificationChannel(notificationChannel);

                if (NotificationChannels.EMAIL_CHANNEL.getChannelType().equals(notificationChannel)) {
                    notificationTemplate.setContentType(
                            omElement.getAttributeValue(new QName(I18nMgtConstants.TEMPLATE_CONTENT_TYPE)));
                    notificationTemplate.setFooter(templateContentMap.get(I18nMgtConstants.TEMPLATE_FOOTER));
                    notificationTemplate.setSubject(templateContentMap.get(I18nMgtConstants.TEMPLATE_SUBJECT));
                }
                defaultNotificationTemplates.add(notificationTemplate);
            }
        } catch (XMLStreamException | IOException e) {
            LOG.warn("Error while loading default templates from file.", e);
        } finally {
            try {
                if (xmlStreamReader != null) {
                    xmlStreamReader.close();
                }
            } catch (XMLStreamException e) {
                LOG.error("Error while closing XML stream", e);
            }
        }
        return defaultNotificationTemplates;
    }

    /**
     * Build the file path of the notification template config file according to the given channel.
     *
     * @param notificationChannel Notification channel name (EMAIL,SMS)
     * @return Path of the configuration file
     */
    private String buildNotificationTemplateConfigPath(String notificationChannel) {

        if (NotificationChannels.SMS_CHANNEL.getChannelType().equals(notificationChannel)) {
            return CarbonUtils.getCarbonConfigDirPath() + File.separator +
                    I18nMgtConstants.SMS_CONF_DIRECTORY + File.separator + I18nMgtConstants.SMS_TEMPLAE_ADMIN_CONF_FILE;
        }
        return CarbonUtils.getCarbonConfigDirPath() + File.separator +
                I18nMgtConstants.EMAIL_CONF_DIRECTORY + File.separator + I18nMgtConstants.EMAIL_ADMIN_CONF_FILE;
    }
}
