/*
 * 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.sdm.application.service;

import com.scytl.products.oscore.logging.api.domain.Level;
import com.scytl.products.oscore.logging.api.domain.LogContent;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
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.RetrofitConsumer;
import com.scytl.products.ov.commons.infrastructure.remote.client.RetrofitException;
import com.scytl.products.ov.commons.path.PathResolver;
import com.scytl.products.ov.commons.util.JsonUtils;
import com.scytl.products.ov.sdm.application.exception.CleansingOutputsDownloadServiceException;
import com.scytl.products.ov.sdm.application.log.SdmSecureLogEvent;
import com.scytl.products.ov.sdm.domain.model.ballotbox.BallotBoxRepository;
import com.scytl.products.ov.sdm.domain.service.ConfigConstants;
import com.scytl.products.ov.sdm.infrastructure.JsonConstants;
import com.scytl.products.ov.sdm.infrastructure.clients.ElectionInformationClient;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.security.PrivateKey;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.json.JsonArray;
import javax.json.JsonObject;
import okhttp3.ResponseBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import retrofit2.Retrofit;

@Service
public class CleansingOutputsDownloadService {

    private static final Logger LOGGER = LoggerFactory.getLogger(CleansingOutputsDownloadService.class);

    @Value("${tenantID}")
    private String tenantId;

    @Value("${EI_URL}")
    private String eiBaseURL;

    @Autowired
    private SecureLoggingWriter secureLogger;

    @Autowired
    private PathResolver pathResolver;

    @Autowired
    private BallotBoxRepository ballotBoxRepository;

    @Autowired
    private KeyStoreService keystoreService;

    private Retrofit restAdapter;

    @PostConstruct
    void configure() {
        try {
            PrivateKey requestSigningKey = keystoreService.getPrivateKey();
            restAdapter = RestClientConnectionManager.getInstance().getRestClientWithInterceptorAndJacksonConverter(
                eiBaseURL, requestSigningKey, NodeIdentifier.SECURE_DATA_MANAGER);
        } catch (OvCommonsInfrastructureException e) {
            LOGGER.error("Failed to initialize RestAdapter. ", e);
            throw new CleansingOutputsDownloadServiceException("Failed to initialize RestAdapter.", e);
        }
    }

    /**
     * Download the cleansing outputs of a ballot box based on election event id
     * and its id.
     *
     * @param electionEventId
     *            the election event id.
     * @param ballotBoxId
     *            the ballot box id.
     * @throws CleansingOutputsDownloadException
     *             if something fails when downloading the cleansing outputs.
     */
    public void download(final String electionEventId, final String ballotBoxId)
            throws CleansingOutputsDownloadException {
        Map<String, Object> criteria = new HashMap<>();
        criteria.put(JsonConstants.JSON_ATTRIBUTE_NAME_ID, ballotBoxId);
        criteria.put(JsonConstants.JSON_STRUCTURE_NAME_ELECTION_EVENT_ID, electionEventId);
        String json = ballotBoxRepository.list(criteria);
        JsonArray ballotBoxes = JsonUtils.getJsonObject(json).getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_RESULT);
        JsonObject ballotBox = ballotBoxes.getJsonObject(0);
        String ballotId = ballotBox.getJsonObject(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOT)
            .getString(JsonConstants.JSON_ATTRIBUTE_NAME_ID);

        ResponseBody successfulVotesResponseBody = downloadSuccessfulVotes(electionEventId, ballotBoxId);

        writeSuccessfulVotesStreamToFile(electionEventId, ballotId, ballotBoxId, successfulVotesResponseBody);

        ResponseBody failedVotesResponseBody = downloadFailedVotes(electionEventId, ballotBoxId);

        writeFailedVotesStreamToFile(electionEventId, ballotId, ballotBoxId, failedVotesResponseBody);
    }

    /**
     * Download successful votes from election information context.
     *
     * @param electionEventId
     *            the election event identifier.
     * @param ballotBoxId
     *            the ballot box identifier.
     * @throws CleansingOutputsDownloadException
     *             if the download of successful votes fails.
     */
    private ResponseBody downloadSuccessfulVotes(String electionEventId, String ballotBoxId)
            throws CleansingOutputsDownloadException {
        ResponseBody responseBody;
        try {
            responseBody = RetrofitConsumer.processResponse(
                getElectionInformationClient().downloadSuccessfulVotes(tenantId, electionEventId, ballotBoxId));
        } catch (final RetrofitException e) {
            String error = String.format("Failed to download successful votes [electionEvent=%s, ballotBox=%s]",
                electionEventId, ballotBoxId);
            secureLogger.log(Level.ERROR,
                new LogContent.LogContentBuilder().objectId(ballotBoxId).electionEvent(electionEventId)
                    .logEvent(SdmSecureLogEvent.SUCCESSFUL_VOTES_DOWNLOADING_FAILED)
                    .additionalInfo("file_name", "successfulVotes.csv")
                    .additionalInfo("err_desc", "Error downloading successful votes. HTTP status: " + e.getHttpCode())
                    .createLogInfo());
            throw new CleansingOutputsDownloadException(error, e);
        }
        return responseBody;
    }

    /**
     * Download failed votes from election information context.
     *
     * @param electionEventId
     *            the election event identifier.
     * @param ballotBoxId
     *            the ballot box identifier.
     * @throws CleansingOutputsDownloadException
     *             if the download of failed votes fails.
     */
    private ResponseBody downloadFailedVotes(String electionEventId, String ballotBoxId)
            throws CleansingOutputsDownloadException {
        ResponseBody response;
        try {
            response = RetrofitConsumer.processResponse(
                getElectionInformationClient().downloadFailedVotes(tenantId, electionEventId, ballotBoxId));
        } catch (final RetrofitException e) {
            String error = String.format("Failed to download failed votes [electionEvent=%s, ballotBox=%s]",
                electionEventId, ballotBoxId);
            secureLogger.log(Level.ERROR,
                new LogContent.LogContentBuilder().objectId(ballotBoxId).electionEvent(electionEventId)
                    .logEvent(SdmSecureLogEvent.FAILED_VOTES_DOWNLOADING_FAILED)
                    .additionalInfo("file_name", "failedVotes.csv")
                    .additionalInfo("err_desc", "Error downloading failed votes. HTTP status: " + e.getHttpCode())
                    .createLogInfo());
            throw new CleansingOutputsDownloadException(error, e);
        }
        return response;
    }

    /**
     * Extracts the successful votes bytes from the stream and write them to
     * temp file.
     *
     * @param responseBody
     *            the stream containing the bsuccessful vote raw bytes
     */
    private void writeSuccessfulVotesStreamToFile(String electionEventId, String ballotId, String ballotBoxId,
            ResponseBody responseBody) throws CleansingOutputsDownloadException {
        try (InputStream stream = responseBody.byteStream()) {
            Path ballotBoxFile = getDownloadedSuccessfulVotesPath(electionEventId, ballotId, ballotBoxId);
            Files.copy(stream, ballotBoxFile, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            String errorMessage = String.format("Failed to write successful votes [electionEvent=%s, ballotBox=%s]",
                electionEventId, ballotBoxId);
            LOGGER.error(errorMessage, e);
            secureLogger.log(Level.ERROR,
                new LogContent.LogContentBuilder().objectId(ballotBoxId).electionEvent(electionEventId)
                    .logEvent(SdmSecureLogEvent.SUCCESSFUL_VOTES_DOWNLOADING_FAILED)
                    .additionalInfo("file_name", "successfulVotes.csv")
                    .additionalInfo("err_desc", "Failed to write successful votes to temp file " + e.getMessage())
                    .createLogInfo());
            throw new CleansingOutputsDownloadException(errorMessage, e);
        }
    }

    /**
     * Extracts the failed votes bytes from the stream and write them to temp
     * file.
     *
     * @param responseBody
     *            the stream containing the failed votes raw bytes
     */
    private void writeFailedVotesStreamToFile(String electionEventId, String ballotId, String ballotBoxId,
            ResponseBody responseBody) throws CleansingOutputsDownloadException {
        try (InputStream stream = responseBody.byteStream()) {
            Path ballotBoxFile = getDownloadedFailedVotesPath(electionEventId, ballotId, ballotBoxId);
            Files.copy(stream, ballotBoxFile, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            String errorMessage = String.format("Failed to write failed votes [electionEvent=%s, ballotBox=%s]",
                electionEventId, ballotBoxId);
            LOGGER.error(errorMessage, e);
            secureLogger.log(Level.ERROR,
                new LogContent.LogContentBuilder().objectId(ballotBoxId).electionEvent(electionEventId)
                    .logEvent(SdmSecureLogEvent.FAILED_VOTES_DOWNLOADING_FAILED)
                    .additionalInfo("file_name", "failedVotes.csv")
                    .additionalInfo("err_desc", "Failed to write failed votes" + e.getMessage()).createLogInfo());
            throw new CleansingOutputsDownloadException(errorMessage, e);
        }
    }

    private Path getDownloadedSuccessfulVotesPath(final String electionEventId, final String ballotId,
            final String ballotBoxId) {
        return pathResolver.resolve(ConfigConstants.CONFIG_FILES_BASE_DIR, electionEventId,
            ConfigConstants.CONFIG_DIR_NAME_ONLINE, ConfigConstants.CONFIG_DIR_NAME_ELECTIONINFORMATION,
            ConfigConstants.CONFIG_DIR_NAME_BALLOTS, ballotId, ConfigConstants.CONFIG_DIR_NAME_BALLOTBOXES, ballotBoxId,
            ConfigConstants.CONFIG_FILE_NAME_SUCCESSFUL_VOTES);
    }

    private Path getDownloadedFailedVotesPath(final String electionEventId, final String ballotId,
            final String ballotBoxId) {
        return pathResolver.resolve(ConfigConstants.CONFIG_FILES_BASE_DIR, electionEventId,
            ConfigConstants.CONFIG_DIR_NAME_ONLINE, ConfigConstants.CONFIG_DIR_NAME_ELECTIONINFORMATION,
            ConfigConstants.CONFIG_DIR_NAME_BALLOTS, ballotId, ConfigConstants.CONFIG_DIR_NAME_BALLOTBOXES, ballotBoxId,
            ConfigConstants.CONFIG_FILE_NAME_FAILED_VOTES);
    }

    /**
     * Returns a client for election information.
     *
     * @return the rest client.
     */
    ElectionInformationClient getElectionInformationClient() {
        return restAdapter.create(ElectionInformationClient.class);
    }

}
