/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.products.ov.config.cli;

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Set;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.scytl.products.ov.commons.beans.domain.model.platform.PlatformInstallationData;
import com.scytl.products.ov.config.commons.exception.InstallPlatformCommandException;
import com.scytl.products.ov.config.install.PlatformDataUploader;
import com.scytl.products.ov.config.io.ServicesInput;
import com.scytl.products.ov.config.utils.FileUtils;
import com.scytl.products.ov.utils.ConfigObjectMapper;

/**
 * Command that attempts to retrieve data from a number of files and then
 * attempts to send that data to endpoints.
 */
public class InstallPlatformCommand implements Command {

    private static final String LOGGING_ENCRYPTION_KEYSTORE_FILENAME_ENDING = "_log_encryption.p12";

    private static final String LOGGING_SIGNING_KEYSTORE_FILENAME_ENDING = "_log_signing.p12";

    private static final Logger LOG = LoggerFactory.getLogger("std");

    /**
     * Retrieve data from files, and then attempt to send that data to the
     * appropriate endpoints.
     * <P>
     * This method obtains a list of services from a configuration file. Each of
     * these services should have an endpoint for receiving incoming platform
     * installation data.
     * <P>
     * For each of the services defined in the services configuration file, this
     * method attempts to read the platform installation data needed by that
     * service (platform root CA certificate and logging keystores) and it
     * constructs the URL of the service endpoint, based on a URL from a
     * properties file, and from the service name.
     *
     * @see com.scytl.products.ov.config.cli.Command#execute(java.util.Map)
     */
    @Override
    public void execute(final Map<String, String> params2values) {

        LOG.info("Starting the platform installation process...");

        final String platformRootCaCertPath = params2values.get(ActionParams.PLATFORM_ROOT_CA_CERT_PATH.getName());
        final String platformRootCaIssuerCertPath =
            params2values.get(ActionParams.PLATFORM_ROOT_CA_ISSUER_CERT_PATH.getName());
        final String loggingKeystoresFolder = params2values.get(ActionParams.LOGGING_KEYSTORES_FOLDER.getName());
        final String servicesNamesPath = params2values.get(ActionParams.SERVICES_NAMES_PATH.getName());
        final String password = params2values.get(ActionParams.PASSWORD.getName());

        LOG.info("Path of directory containing logging keystores: " + loggingKeystoresFolder);

        final Path servicesPropertiesPath;
        try {
            servicesPropertiesPath = Paths.get(Paths.get(servicesNamesPath).toFile().getCanonicalPath());
        } catch (IOException e) {
			throw new InstallPlatformCommandException("Error while trying to obtain path of services properties file",
					e);
        }
        LOG.info("Path of services properties file: " + servicesPropertiesPath.toString());

        Path pathPlatformRootCa = Paths.get(platformRootCaCertPath).toAbsolutePath();
        LOG.info("Path of platform root CA certificate: " + pathPlatformRootCa.toString());
        String platformRootCaAsPem;
        try {
            platformRootCaAsPem = getPlatformRootCaAsPem(pathPlatformRootCa);
        } catch (IOException e) {
			throw new InstallPlatformCommandException("Error while trying to obtain the platform root CA in PEM format",
					e);
        }
        String platformRootIssuerCaPEM = null;
        if (platformRootCaIssuerCertPath != null && !platformRootCaIssuerCertPath.isEmpty()) {
            Path pathPlatformRootIssuerCa = Paths.get(platformRootCaIssuerCertPath).toAbsolutePath();
            LOG.info("Path of platform root issuer CA certificate: " + platformRootCaIssuerCertPath);
            try {
                platformRootIssuerCaPEM = getPlatformRootCaAsPem(pathPlatformRootIssuerCa);
            } catch (IOException e) {
				throw new InstallPlatformCommandException(
						"Error while trying to obtain the platform root issuer CA in PEM format", e);
            }
        }
        ServicesInput servicesInput = getServicesNameFromJson(servicesPropertiesPath);

        if (installPlatformOnAllContexts(platformRootCaAsPem, platformRootIssuerCaPEM, loggingKeystoresFolder,
            servicesInput.getServiceNames(), password)) {
            LOG.info("Overall install platform result: SUCCESS");
        } else {
            LOG.error("Overall install platform result: FAILED");
            LOG.error("Troubleshooting tips:");
            LOG.error("Is the server running?");
            LOG.error("Does the relevant properties file have the correct values? (such as port number)");
            LOG.error("Could the data already exist in the database?");
        }
    }

    private String getPlatformRootCaAsPem(final Path path) throws IOException {

        FileInputStream fisTargetFile = new FileInputStream(path.toFile());
        return IOUtils.toString(fisTargetFile, "UTF-8");
    }

    private boolean installPlatformOnAllContexts(final String platformRootCaAsPem, final String platformRootIssuerCaPEM,
            final String loggingKeystoresFolder, final Set<String> serviceNames, final String password) {

        boolean result = true;

        PlatformDataUploader platformDataUploader = new PlatformDataUploader();

        for (String serviceName : serviceNames) {

            LOG.info("Attempting to install platform in " + serviceName + "...");

            String loggingEncryptionKeystoreBase64 =
                getFileContentsBase64(serviceName, LOGGING_ENCRYPTION_KEYSTORE_FILENAME_ENDING, loggingKeystoresFolder);
            String loggingSigningKeystoreBase64 =
                getFileContentsBase64(serviceName, LOGGING_SIGNING_KEYSTORE_FILENAME_ENDING, loggingKeystoresFolder);

            // construct object to send
            PlatformInstallationData platformInstallationData = new PlatformInstallationData();
            platformInstallationData.setLoggingEncryptionKeystoreBase64(loggingEncryptionKeystoreBase64);
            platformInstallationData.setLoggingSigningKeystoreBase64(loggingSigningKeystoreBase64);
            platformInstallationData.setPlatformRootCaPEM(platformRootCaAsPem);
            platformInstallationData.setPlatformRootIssuerCaPEM(platformRootIssuerCaPEM);

            // send object to endpoint of context
            boolean thisResult = platformDataUploader.upload(serviceName, platformInstallationData, password);

            if (thisResult) {
                LOG.info("Successfully installed to platform: " + serviceName);
            } else {
                LOG.error("Failed to install to platform: " + serviceName);
            }

            result = result && thisResult;
        }

        return result;
    }

    private String getFileContentsBase64(final String serviceName, final String fileEnding,
            final String loggingKeystoresFolder) {

        String keystorePathAsString = serviceName + fileEnding;
        Path keystorePath = Paths.get(loggingKeystoresFolder, keystorePathAsString).toAbsolutePath();
        String keystoreBase64;
        try {
            keystoreBase64 = FileUtils.getFileBytesAsBase64(keystorePath);
        } catch (IOException e) {
            throw new InstallPlatformCommandException(
                "Error while trying to obtain a logging keystore for the service: " + serviceName, e);
        }

        return keystoreBase64;
    }

    private ServicesInput getServicesNameFromJson(final Path pathToJson) {

        ConfigObjectMapper configObjectMapper = new ConfigObjectMapper();
        ServicesInput servicesInput = null;
        try {
            servicesInput = configObjectMapper.fromJSONFileToJava(pathToJson.toFile(), ServicesInput.class);
        } catch (IOException e) {
			throw new InstallPlatformCommandException("An error occurred while loading the services file", e);
        }

        return servicesInput;
    }
}
