/*
 * 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.sdm.application.log.SdmSecureLogEvent;
import java.util.HashMap;
import java.util.Map;

import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;


import com.scytl.products.ov.sdm.application.exception.DatabaseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.scytl.products.ov.commons.util.JsonUtils;
import com.scytl.products.ov.sdm.domain.model.ballot.BallotRepository;
import com.scytl.products.ov.sdm.domain.model.ballot.BallotUploadRepository;
import com.scytl.products.ov.sdm.domain.model.ballottext.BallotTextRepository;
import com.scytl.products.ov.sdm.domain.model.electionevent.ElectionEventRepository;
import com.scytl.products.ov.sdm.domain.model.status.Status;
import com.scytl.products.ov.sdm.domain.model.status.SynchronizeStatus;
import com.scytl.products.ov.sdm.infrastructure.JsonConstants;

/**
 * Service responsible of uploading ballots and ballot texts
 */
@Service
public class BallotUploadService {

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

	private static final String NULL_ELECTION_EVENT_ID = "";

	private static final String BALLOT_ID_FIELD = "ballot.id";

	@Autowired
	private ElectionEventRepository electionEventRepository;

	@Autowired
	private BallotRepository ballotRepository;

	@Autowired
	private BallotTextRepository ballotTextRepository;

	@Autowired
	private BallotUploadRepository ballotUploadRepository;

	@Autowired
    private SecureLoggingWriter secureLogger;

    /**
     * Uploads the available ballots and ballot texts to the voter portal.
     */
    public void uploadSyncronizableBallots(String eeid) {

		Map<String, Object> ballotParams = new HashMap<>();

		addSignedBallots(ballotParams);
		addPendingToSynchBallots(ballotParams);

		if (thereIsAnElectionEventAsAParameter(eeid)) {
			addBallotsOfElectionEvent(eeid, ballotParams);
		}

		String ballotDocuments = ballotRepository.list(ballotParams);
		JsonArray ballots = JsonUtils.getJsonObject(ballotDocuments).getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_RESULT);

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

			JsonObject ballotInArray = ballots.getJsonObject(i);
			String signedBallot = ballotInArray.getString(JsonConstants.JSON_ATTRIBUTE_NAME_SIGNED_OBJECT);

			String electionEventId =
				ballotInArray.getJsonObject(JsonConstants.JSON_ATTRIBUTE_NAME_ELECTION_EVENT).getString(
					JsonConstants.JSON_ATTRIBUTE_NAME_ID);
			String ballotId = ballotInArray.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);

			Map<String, Object> ballotTextParams = new HashMap<>();
			ballotTextParams.put(BALLOT_ID_FIELD, ballotId);

			LOGGER.info("Loading the signed ballot");
			JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
			for (String signature : ballotTextRepository.listSignatures(ballotTextParams)) {
                JsonObject object =
                    Json.createObjectBuilder()
                        .add(
                            JsonConstants.JSON_ATTRIBUTE_NAME_SIGNED_OBJECT,
                            signature)
                        .build();
                arrayBuilder.add(object);
			}
			JsonArray signedBallotTexts = arrayBuilder.build();
			LOGGER.info("Loading the signed ballot texts");
			JsonObject jsonInput = Json.createObjectBuilder().add(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOT, signedBallot)
				.add(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOT_TEXTS_UPLOAD, signedBallotTexts.toString()).build();

			LOGGER.info("Uploading the signed ballot and ballot texts");
			boolean result = ballotUploadRepository.uploadBallot(jsonInput, electionEventId, ballotId, adminBoardId);
			JsonObjectBuilder jsonObjectBuilder = Json.createObjectBuilder();
			if (result) {

				LOGGER.info("Changing the state of the ballot");
				jsonObjectBuilder.add(JsonConstants.JSON_ATTRIBUTE_NAME_ID, ballotId);
				jsonObjectBuilder
					.add(JsonConstants.JSON_ATTRIBUTE_NAME_SYNCHRONIZED, SynchronizeStatus.SYNCHRONIZED.getIsSynchronized().toString());
				jsonObjectBuilder
					.add(JsonConstants.JSON_ATTRIBUTE_NAME_DETAILS, SynchronizeStatus.SYNCHRONIZED.getStatus());
				LOGGER.info("The signed ballot was uploaded successfully");

                secureLogger.log(Level.INFO,
                        new LogContent.LogContentBuilder()
                                .objectId(ballotId).electionEvent(electionEventId)
                                .logEvent(SdmSecureLogEvent.SIGNED_BALLOT_AND_TEXT_UPLOADED_SUCCESSFULLY)
                                .additionalInfo("b_id", ballotId).createLogInfo());
			} else {
                String error = "An error occurred while uploading the signed ballot";
                LOGGER.error(error);
                jsonObjectBuilder.add(JsonConstants.JSON_ATTRIBUTE_NAME_ID, ballotId);
                jsonObjectBuilder.add(JsonConstants.JSON_ATTRIBUTE_NAME_DETAILS, SynchronizeStatus.FAILED.getStatus());

                secureLogger.log(Level.ERROR,
                        new LogContent.LogContentBuilder().objectId(ballotId).electionEvent(electionEventId)
                                .logEvent(SdmSecureLogEvent.SIGNED_BALLOT_AND_TEXT_UPLOADING_FAILED)
                                .additionalInfo("b_id", ballotId).additionalInfo("err_desc", error).createLogInfo());
			}

			try {
				ballotRepository.update(jsonObjectBuilder.build().toString());
			} catch (DatabaseException ex) {
				LOGGER.error("An error occurred while updating the signed ballot", ex);
				secureLogger.log(Level.ERROR, new LogContent.LogContentBuilder()
						.logEvent(SdmSecureLogEvent.SYNCHRONIZING_WITH_VP_FAILED)
						.electionEvent(electionEventId)
						.additionalInfo("err_desc", ex.getMessage())
						.createLogInfo());
			}

		}

	}

	private void addBallotsOfElectionEvent(final String electionEvent, final Map<String, Object> ballotParams) {
		ballotParams.put(JsonConstants.JSON_STRUCTURE_NAME_ELECTION_EVENT_ID, electionEvent);
	}

	private void addPendingToSynchBallots(final Map<String, Object> ballotParams) {
		ballotParams.put(JsonConstants.JSON_ATTRIBUTE_NAME_SYNCHRONIZED,
			SynchronizeStatus.PENDING.getIsSynchronized().toString());
	}

	private void addSignedBallots(final Map<String, Object> ballotParams) {
		ballotParams.put(JsonConstants.JSON_ATTRIBUTE_NAME_STATUS, Status.SIGNED.name());
	}

	private boolean thereIsAnElectionEventAsAParameter(final String electionEvent) {
		return !NULL_ELECTION_EVENT_ID.equals(electionEvent);
	}

}
