/*
 * 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.install;

import java.io.IOException;
import java.io.InputStream;
import java.security.PrivateKey;
import java.util.Properties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.primitives.PrimitivesServiceAPI;
import com.scytl.cryptolib.primitives.service.PrimitivesService;
import com.scytl.cryptolib.securerandom.cryptoapi.CryptoAPIRandomString;
import com.scytl.products.ov.commons.beans.domain.model.platform.PlatformInstallationData;
import com.scytl.products.ov.commons.infrastructure.exception.OvCommonsInfrastructureException;
import com.scytl.products.ov.commons.infrastructure.remote.client.NodeIdentifier;
import com.scytl.products.ov.commons.infrastructure.remote.client.RestClientConnectionManager;
import com.scytl.products.ov.commons.infrastructure.remote.client.RetrofitException;
import com.scytl.products.ov.config.commons.ConfigPlatformRootKeyLoader;
import com.scytl.products.ov.config.commons.exception.ConfigPlatformRootException;
import com.scytl.products.ov.config.commons.exception.PlataformDataUploaderException;

import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;

import okhttp3.ResponseBody;

/**
 * Provides functionality for uploading data to a context.
 */
public class PlatformDataUploader {

    private static final String CONTEXT_URL = "CONTEXT_URL";

    private static final String PATH_OF_PLATFORM_INSTALLATION_PROPERTIES_FILE = "platformInstallation.properties";

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

    public static final int TRACKING_ID_LENGTH = 16;

    private Properties props;

    /**
     * Upload the data encapsulated within {@code platformInstallationData} to
     * the endpoint represented by {@serviceName}.
     * <P>
     * This class reads the URL of the destination endpoint from a properties
     * file, and it then modifies that URL using the value of
     * {@code serviceName}.
     *
     * @param serviceName
     *            the name of the service (the context).
     * @param platformInstallationData
     *            the data to be uploaded.
     * @param password
     *            Trusted build KeyStore password.
     * @return true if the upload is performed without any errors, false
     *         otherwise.
     */
    public boolean upload(final String serviceName, final PlatformInstallationData platformInstallationData,
            final String password) {

        LOG.info("Going to attempt to install platform on context " + serviceName);

        try {
            String trackingId = generateTrackingId();
            InstallPlatformClient client = createClient(serviceName, password);
            Call<ResponseBody> postToContext = client.postToContext(trackingId, platformInstallationData);
            Response<ResponseBody> execute = null;
    		try {
    			execute = postToContext.execute();
    		} catch (IOException e) {
    			throw new RetrofitException(404, e.getMessage(), e);
    		}
    		if (!execute.isSuccessful()) {
    			throw new RetrofitException(execute.code(), execute.errorBody());
    		}
        } catch (RetrofitException e) {
            // in case of connection error (invalid host, etc..)
            if (e.getErrorBody() != null && e.getErrorBody().contentLength() == 0) {
                LOG.error("Network failure sending data to service", e);
            } else {
                switch (e.getHttpCode()) {

                case 409:
                    LOG.error("An error occurred while trying to upload data to {}. "
                        + "The data already existed for this service.", serviceName, e);

                    break;
                case 404:
                    LOG.error("An error occurred while trying to upload data to {}. "
                        + "The service was not found at the host.", serviceName, e);
                    break;
                default:
                    LOG.error("An error occurred trying to upload data.", e);
                    break;

                }
            }
            return false;
        } catch (GeneralCryptoLibException e) {
            LOG.error("Failed to generate required TrackingId value", e);
            return false;
        }
        return true;
    }

    private String generateTrackingId() throws GeneralCryptoLibException {
        PrimitivesServiceAPI primitivesService = new PrimitivesService();
        CryptoAPIRandomString randomString = primitivesService.get32CharAlphabetCryptoRandomString();
        return randomString.nextRandom(TRACKING_ID_LENGTH);
    }

    private InstallPlatformClient createClient(final String serviceName, final String password) {

        props = new Properties();
        try (InputStream input = Thread.currentThread().getContextClassLoader()
            .getResourceAsStream(PATH_OF_PLATFORM_INSTALLATION_PROPERTIES_FILE)) {
            props.load(input);
        } catch (IOException e) {
			throw new PlataformDataUploaderException(
					"An error occurred while loading the platform installation properties", e);
        }

        String contextSpecificURL = props.getProperty(CONTEXT_URL).replace("XX", serviceName).toLowerCase();

        LOG.info("Sending platform installation data to endpoint: " + contextSpecificURL);

        Retrofit client;
        try {

            ConfigPlatformRootKeyLoader configPlatformRootKeyLoader = ConfigPlatformRootKeyLoader.getInstance();
            PrivateKey privateKey = configPlatformRootKeyLoader.getPrivateKeyFromKeystore(password);

            client = RestClientConnectionManager.getInstance().getRestClientWithInterceptorAndJacksonConverter(
                contextSpecificURL, privateKey, NodeIdentifier.CONFIG_PLATFORM_ROOT);

        } catch (OvCommonsInfrastructureException | ConfigPlatformRootException e) {
            String errorMsg = "Error occurred while trying to obtain a REST client.";
            LOG.error(errorMsg);
            throw new PlataformDataUploaderException(errorMsg, e);
        }

        return client.create(InstallPlatformClient.class);

    }

}
