/*
 * 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.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.primitives.primes.utils.PemUtils;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.configuration.JSONSignatureConfiguration;
import com.scytl.products.ov.commons.sign.JSONSigner;
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.ballottext.BallotTextRepository;
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;
import com.scytl.products.ov.sdm.infrastructure.service.ConfigurationEntityStatusService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonValue;
import java.security.PrivateKey;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;

/**
 * Service for operates with ballots.
 */
@Service
public class BallotService {

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

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

	@Autowired
	private ConfigurationEntityStatusService statusService;

	@Autowired
	private BallotRepository ballotRepository;

	@Autowired
	private BallotTextRepository ballotTextRepository;

	private JSONSigner signer;

	@PostConstruct
	public void init() {
		signer = new JSONSigner(JSONSignatureConfiguration.RSA_PSS_SHA256);

	}

	/**
	 * Sign the given ballot and related ballot texts, and change the state of the ballot from locked to SIGNED for a given
	 * election event and ballot id.
	 * 
	 * @param electionEventId the election event id.
	 * @param ballotId the ballot id.
	 * @param privateKeyPEM the administration board private key in PEM format.
	 * @throws ResourceNotFoundException if the ballot is not found.
	 * @throws GeneralCryptoLibException if the private key cannot be read.
	 */
	public void sign(String electionEventId, String ballotId, final String privateKeyPEM)
			throws ResourceNotFoundException, GeneralCryptoLibException {

		PrivateKey privateKey = PemUtils.privateKeyFromPem(privateKeyPEM);

		JsonObject ballot = getValidBallot(electionEventId, ballotId);
		JsonObject modifiedBallot = removeBallotMetaData(ballot);

		LOGGER.info("Signing ballot " + ballotId);
		String signedBallot = signer.sign(privateKey, modifiedBallot.toString());

		ballotRepository.updateSignedBallot(ballotId, signedBallot);
		JsonArray ballotTexts = getBallotTexts(ballotId);

		LOGGER.info("Signing ballot texts");
		for (int i = 0; i < ballotTexts.size(); i++) {

			JsonObject ballotText = ballotTexts.getJsonObject(i);
			String ballotTextId = ballotText.getString(JsonConstants.JSON_ATTRIBUTE_NAME_ID);
			JsonObject modifiedBallotText = removeBallotTextMetaData(ballotText);
			String signedBallotText = signer.sign(privateKey, modifiedBallotText.toString());
			ballotTextRepository.updateSignedBallotText(ballotTextId, signedBallotText);
		}

		LOGGER.info("Changing the ballot status");
		statusService.updateWithSynchronizedStatus(Status.SIGNED.name(), ballotId, ballotRepository,
			SynchronizeStatus.PENDING);

		LOGGER.info("The ballot was successfully signed");
	}

	private JsonObject removeBallotTextMetaData(final JsonObject ballotText) {
		return removeField(JsonConstants.JSON_ATTRIBUTE_NAME_SIGNED_OBJECT, ballotText);
	}

	private JsonObject removeBallotMetaData(final JsonObject ballot) {

		JsonObject modifiedBallot = removeField(JsonConstants.JSON_ATTRIBUTE_NAME_STATUS, ballot);
		modifiedBallot = removeField(JsonConstants.JSON_ATTRIBUTE_NAME_DETAILS, modifiedBallot);
		modifiedBallot = removeField(JsonConstants.JSON_ATTRIBUTE_NAME_SYNCHRONIZED, modifiedBallot);
		return removeField(JsonConstants.JSON_ATTRIBUTE_NAME_SIGNED_OBJECT, modifiedBallot);
	}

	private JsonObject removeField(String field, JsonObject obj) {

		JsonObjectBuilder builder = Json.createObjectBuilder();

		for (Iterator<Map.Entry<String, JsonValue>> it = obj.entrySet().iterator(); it.hasNext();) {
			Map.Entry<String, JsonValue> e = it.next();
			String key = e.getKey();
			JsonValue value = e.getValue();
			if (!key.equals(field)) {
				builder.add(key, value);
			}

		}
		return builder.build();
	}

	private JsonArray getBallotTexts(final String ballotId) {
		Map<String, Object> ballotTextParams = new HashMap<>();
		ballotTextParams.put(BALLOT_ID_FIELD, ballotId);
		return JsonUtils.getJsonObject(ballotTextRepository.list(ballotTextParams))
			.getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_RESULT);
	}

	private JsonObject getValidBallot(final String electionEventId, final String ballotId)
			throws ResourceNotFoundException {

		Optional<JsonObject> possibleBallot = getPossibleValidBallot(electionEventId, ballotId);

		if (!possibleBallot.isPresent()) {
			throw new ResourceNotFoundException("Ballot not found");
		}

		return possibleBallot.get();
	}

	/**
	 * Pre: there is just one matching element
	 *
	 * @param electionEventId
	 * @param ballotId
	 * @return single {@link Status.LOCKED} ballot in json object format
	 * @throws ResourceNotFoundException
	 */
	private Optional<JsonObject> getPossibleValidBallot(String electionEventId, String ballotId) {

		Optional<JsonObject> ballot = Optional.empty();
		Map<String, Object> attributeValueMap = new HashMap<>();

		attributeValueMap.put(JsonConstants.JSON_STRUCTURE_NAME_ELECTION_EVENT_ID, electionEventId);
		attributeValueMap.put(JsonConstants.JSON_ATTRIBUTE_NAME_ID, ballotId);
		attributeValueMap.put(JsonConstants.JSON_ATTRIBUTE_NAME_STATUS, Status.LOCKED.name());
		String ballotResultListAsJson = ballotRepository.list(attributeValueMap);

		if (StringUtils.isEmpty(ballotResultListAsJson)) {
			return ballot;
		} else {
			JsonArray ballotResultList =
				JsonUtils.getJsonObject(ballotResultListAsJson).getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_RESULT);

			if (ballotResultList != null && !ballotResultList.isEmpty()) {
				ballot = Optional.of(ballotResultList.getJsonObject(0));
			} else {
				return ballot;
			}
		}
		return ballot;
	}
}
