/*
 * 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.path.PathResolver;
import com.scytl.products.ov.commons.ui.ws.rs.ObjectMappers;
import com.scytl.products.ov.commons.util.JsonUtils;
import com.scytl.products.ov.commons.validation.ValidationResult;
import com.scytl.products.ov.sdm.application.log.SdmSecureLogEvent;
import com.scytl.products.ov.sdm.domain.model.electionevent.ElectionEventRepository;
import com.scytl.products.ov.sdm.domain.model.keytranslation.BallotBoxTranslation;
import com.scytl.products.ov.sdm.domain.service.ConfigConstants;
import com.scytl.products.ov.sdm.infrastructure.JsonConstants;

import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
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 javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.text.MessageFormat;

/**
 * Service for upload ballot box informations.
 */
@Service
public class BallotBoxInformationsUploadService {

    public static final String ADMIN_BOARD_ID_PARAM = "adminBoardId";

	private static final String ENDPOINT_BALLOT_BOX_CONTENTS = "/ballotboxdata/tenant/{tenantId}/electionevent/{electionEventId}/ballotbox/{ballotBoxId}/adminboard"
			+ "/{adminBoardId}";

	private static final String ENDPOINT_CHECK_IF_BALLOT_BOXES_EMPTY = "/ballotboxes/tenant/{tenantId}/electionevent/{electionEventId}/ballotbox/{ballotBoxId}/status";

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

    private static final String CONSTANT_BALLOT_BOX_DATA = "ballotBox";

    private static final String CONSTANT_BALLOT_BOX_CONTENT_DATA = "ballotBoxContextData";

    private static final String TENANT_ID_PARAM = "tenantId";

    private static final String ELECTION_EVENT_ID_PARAM = "electionEventId";

    private static final String BALLOT_BOX_ID_PARAM = "ballotBoxId";

    @Autowired
    private BallotBoxService ballotBoxService;

    @Autowired
    private PathResolver pathResolver;

    @Autowired
    private ElectionEventRepository electionEventRepository;

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

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

    @Autowired
    private KeyTranslationUploadService keyTranslationUploadService;

    @Autowired
    private SecureLoggingWriter secureLogger;

    /**
     * Uploads additional informations of all ballot boxes pending for upload to
     * the voting portal
     */
    public void uploadSyncronizableBallotBoxInformations(String eeid) {

        JsonArray ballotBoxes = ballotBoxService.getBallotBoxesReadyToSynchronize(eeid);

        for (int i = 0; i < ballotBoxes.size(); i++) {

            JsonObject ballotBox = ballotBoxes.getJsonObject(i);

            String ballotBoxId = ballotBox.getString(JsonConstants.JSON_ATTRIBUTE_NAME_ID);
            String electionEventId = ballotBox.getJsonObject(JsonConstants.JSON_ATTRIBUTE_NAME_ELECTION_EVENT)
                .getString(JsonConstants.JSON_ATTRIBUTE_NAME_ID);

            JsonObject electionEvent = JsonUtils.getJsonObject(electionEventRepository.find(electionEventId));
            JsonObject adminBoard =
                electionEvent.getJsonObject(JsonConstants.JSON_ATTRIBUTE_NAME_ADMINISTRATION_AUTHORITY);

            String adminBoardId = adminBoard.getString(JsonConstants.JSON_ATTRIBUTE_NAME_ID);

            if (isBallotBoxEmpty(electionEventId, ballotBoxId)) {
                String ballotId = ballotBox.getJsonObject(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOT)
                    .getString(JsonConstants.JSON_ATTRIBUTE_NAME_ID);

                try {
					boolean uploadResultBBInformation = uploadBallotBoxConfiguration(electionEventId, ballotBoxId,
							ballotId, adminBoardId);
					
					uploadResultBallotBoxInformation(uploadResultBBInformation, electionEventId, ballotBoxId,
							ballotBox);
                    
                } catch (IOException e) {
					LOGGER.error(MessageFormat.format(
							"Error trying to find ballot box configuration to upload for ballot box id {0}, skipping: {1} ",
							ballotBoxId, e));
					secureLogger.log(Level.ERROR,
							new LogContent.LogContentBuilder().logEvent(SdmSecureLogEvent.SYNCHRONIZING_WITH_VP_FAILED)
									.electionEvent(electionEventId)
									.additionalInfo("err_desc",
											"Error trying to find ballot box configuration to upload for ballot box: "
													+ e.getMessage())
									.createLogInfo());
				}
            } else {
                LOGGER.info("Updating the synchronization status of the ballot box");
                ballotBoxService.updateSynchronizationStatus(ballotBoxId, false);
            }
        }
    }

    /**
     * Uploads ballot box configuration
     * 
     * @throws IOException
     */
    private boolean uploadBallotBoxConfiguration(String electionEventId, String ballotBoxId, String ballotId,
            final String adminBoardId) throws IOException {

        LOGGER.info("Loading the signed ballot box configuration");
        JsonObject ballotBoxConfiguration = loadFilesToUpload(electionEventId, ballotBoxId, ballotId);
        LOGGER.info("Uploading the signed ballot box configuration");
        Response response =
            uploadBallotBoxConfiguration(electionEventId, ballotBoxId, adminBoardId, ballotBoxConfiguration);
        return response.getStatus() == Response.Status.OK.getStatusCode();

    }

    private Response uploadBallotBoxConfiguration(final String electionEventId, final String ballotBoxId,
            final String adminBoardId, final JsonObject ballotBoxConfiguration) {
        WebTarget target = ClientBuilder.newClient().target(electionInformationBaseURL + ENDPOINT_BALLOT_BOX_CONTENTS);
        return target.resolveTemplate(TENANT_ID_PARAM, tenantId)
            .resolveTemplate(ELECTION_EVENT_ID_PARAM, electionEventId).resolveTemplate(BALLOT_BOX_ID_PARAM, ballotBoxId)
            .resolveTemplate(ADMIN_BOARD_ID_PARAM, adminBoardId).request()
            .post(Entity.entity(ballotBoxConfiguration.toString(), MediaType.APPLICATION_JSON_TYPE));
    }

	/**
	 * Upload Result Ballot Box Information
	 * 
	 * @param uploadResultBBInformation
	 * @param electionEventId
	 * @param ballotBoxId
	 * @param adminBoardId
	 * @param ballotBox
	 */
    private void uploadResultBallotBoxInformation(boolean uploadResultBBInformation, String electionEventId,
			String ballotBoxId, JsonObject ballotBox) {

		if (uploadResultBBInformation) {
			secureLogger.log(Level.INFO,
					new LogContent.LogContentBuilder().objectId(ballotBoxId)
							.logEvent(SdmSecureLogEvent.SIGNED_BALLOT_BOX_CONFIG_UPLOADED_SUCCESSFULLY)
							.additionalInfo("bb_id", ballotBoxId).createLogInfo());
		} else {
			secureLogger.log(Level.ERROR, new LogContent.LogContentBuilder().objectId(ballotBoxId)
					.logEvent(SdmSecureLogEvent.SIGNED_BALLOT_BOX_UPLOADING_FAILED).additionalInfo("bb_id", ballotBoxId)
					.additionalInfo("err_desc", "Error ocurred when uploading signed ballot box").createLogInfo());
		}
		
		BallotBoxTranslation ballotBoxTranslation = ballotBoxService.createBallotBoxTranslation(ballotBox, ballotBoxId,
				electionEventId);
		boolean uploadKeyTranslation = keyTranslationUploadService.createUpdateBallotBoxKeyTranslations(tenantId,
				ballotBoxTranslation);
		LOGGER.info("Updating the synchronization status of the ballot box");
		ballotBoxService.updateSynchronizationStatus(ballotBoxId, uploadResultBBInformation && uploadKeyTranslation);
		LOGGER.info("The ballot box configuration was uploaded successfully");
            	
    }
                
    
    /**
     * Retrieve json files on filesystem to upload
     */
    private JsonObject loadFilesToUpload(String electionEventId, String ballotBoxId, String ballotId)
            throws IOException {

        JsonFactory jsonFactory = new JsonFactory();
        ObjectMapper jsonMapper = new ObjectMapper(jsonFactory);

        Path ballotBoxConfigurationFilesPath =
            getCommonPathForConfigurationFiles(electionEventId, ballotBoxId, ballotId);
        JsonNode ballotBoxNode = getBallotBoxSignature(jsonFactory, jsonMapper, ballotBoxConfigurationFilesPath);
        JsonNode ballotBoxContextDataNode =
            getBallotBoxContextDataSignature(jsonFactory, jsonMapper, ballotBoxConfigurationFilesPath);

        return (Json.createObjectBuilder().add(CONSTANT_BALLOT_BOX_DATA, ballotBoxNode.toString())
            .add(CONSTANT_BALLOT_BOX_CONTENT_DATA, ballotBoxContextDataNode.toString())).build();
    }

    private JsonNode getBallotBoxContextDataSignature(final JsonFactory jsonFactory, final ObjectMapper jsonMapper,
            final Path ballotBoxConfigurationFilesPath) throws IOException {
        return jsonMapper.readTree(jsonFactory.createJsonParser(new File(ballotBoxConfigurationFilesPath
            .resolve(ConfigConstants.CONFIG_DIR_NAME_SIGNED_BALLOTBOX_CONTEXT_DATA_JSON).toAbsolutePath().toString())));
    }

    private JsonNode getBallotBoxSignature(final JsonFactory jsonFactory, final ObjectMapper jsonMapper,
            final Path ballotBoxConfigurationFilesPath) throws IOException {
        return jsonMapper.readTree(jsonFactory.createJsonParser(new File(ballotBoxConfigurationFilesPath
            .resolve(ConfigConstants.CONFIG_DIR_NAME_SIGNED_BALLOTBOX_JSON).toAbsolutePath().toString())));
    }

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

    private boolean isBallotBoxEmpty(String electionEvent, String ballotBoxId) {

        Boolean result = Boolean.FALSE;
        WebTarget target =
            ClientBuilder.newClient().target(electionInformationBaseURL + ENDPOINT_CHECK_IF_BALLOT_BOXES_EMPTY);

        Response response =
            target.resolveTemplate(TENANT_ID_PARAM, tenantId).resolveTemplate(ELECTION_EVENT_ID_PARAM, electionEvent)
                .resolveTemplate(BALLOT_BOX_ID_PARAM, ballotBoxId).request(MediaType.APPLICATION_JSON).get();

        if (response.getStatus() == Response.Status.OK.getStatusCode()) {
            String json = response.readEntity(String.class);
            ValidationResult validationResult = new ValidationResult();
            try {
                validationResult = ObjectMappers.fromJson(json, ValidationResult.class);
            } catch (IOException e) {
                LOGGER.error("Error checking if a ballot box is empty", e);
            }
            result = validationResult.isResult();
        }
        return result;
    }
}
