/*
 * 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.ei.services.domain.service.vote;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;

import javax.ejb.Stateless;
import javax.enterprise.inject.Any;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.json.JsonObject;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
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.beans.domain.model.vote.Vote;
import com.scytl.products.ov.commons.beans.exceptions.ApplicationException;
import com.scytl.products.ov.commons.beans.exceptions.ApplicationExceptionMessages;
import com.scytl.products.ov.commons.beans.exceptions.DuplicateEntryException;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.beans.exceptions.ValidationException;
import com.scytl.products.ov.commons.beans.validation.ValidationErrorType;
import com.scytl.products.ov.commons.domain.model.rule.AbstractRule;
import com.scytl.products.ov.commons.domain.service.RuleExecutor;
import com.scytl.products.ov.commons.logging.service.VoteHashService;
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.ValidationError;
import com.scytl.products.ov.commons.validation.ValidationResult;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBox;
import com.scytl.products.ov.ei.services.domain.model.validation.VoteValidation;
import com.scytl.products.ov.ei.services.domain.model.validation.VoteValidationRepository;
import com.scytl.products.ov.ei.services.infrastructure.log.ElectionInformationLogConstants;
import com.scytl.products.ov.ei.services.infrastructure.log.ElectionInformationLogEvents;

/**
 * Handles operations on vote validation.
 */
@Stateless(name = "ei-validationService")
public class VoteValidationServiceImpl implements VoteValidationService {

    private static final String VOTE = "vote";

    private final Collection<AbstractRule<Vote>> rules = new ArrayList<>();

    @Inject
    private VoteValidationRepository voteValidationRepository;

    @Inject
    private VoteHashService voteHashService;

    @Inject
    private RuleExecutor<Vote> ruleExecutor;

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

    @Inject
    private SecureLoggingWriter secureLoggerWriter;

    @Inject
    @Any
    void setRules(Instance<AbstractRule<Vote>> instance) {
        for (AbstractRule<Vote> rule : instance) {
            rules.add(rule);
        }
    }

    /**
     * @see com.scytl.products.ov.ei.services.domain.service.vote.VoteValidationService#validate(
     *      com.scytl.products.ov.commons.beans.domain.model.vote.Vote,
     *      java.lang.String, java.lang.String, java.lang.String)
     */
    @Override
    public ValidationResult validate(Vote vote, String tenantId, String electionEventId, String ballotId)
            throws ApplicationException, ResourceNotFoundException {
        validateInput(vote, tenantId, ballotId);
        ValidationError executorRulesResult = ruleExecutor.execute(rules, vote);
        ValidationResult validationResult = new ValidationResult();
        boolean result = executorRulesResult.getValidationErrorType().equals(ValidationErrorType.SUCCESS);
        validationResult.setResult(result);
        validationResult.setValidationError(executorRulesResult);

        if (result) {
            try {
                VoteValidation voteValidation = new VoteValidation();
                voteValidation.setTenantId(tenantId);
                voteValidation.setElectionEventId(electionEventId);
                voteValidation.setVotingCardId(vote.getVotingCardId());
                String voteHash = voteHashService.hash(vote);
                voteValidation.setVoteHash(voteHash);
                voteValidationRepository.save(voteValidation);
                LOG.info("All vote validations OK: the hash of the vote was stored.");
            } catch (GeneralCryptoLibException | DuplicateEntryException e) {
                LOG.error("Error computing the hash of the vote in validation ", e);
            }
        }

        return validationResult;
    }

    // Validates whether the input is correct
    private void validateInput(Vote vote, String tenantId, String ballotId) throws ApplicationException {
        // it is an exception if the input parameters are not valid, because
        // normally they should be already validated when
        // passed to this method, as this service does not validate data
        if (vote == null) {
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_VOTE_IS_NULL);
        }
        if (tenantId == null) {
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_TENANT_ID_IS_NULL);
        }
        if (ballotId == null) {
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_BALLOT_ID_IS_NULL);
        }
    }

    /**
     * @see com.scytl.products.ov.ei.services.domain.service.vote.VoteValidationService#isValid(com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBox)
     */
    @Override
    public boolean isValid(BallotBox ballotBox) throws ValidationException {
        // recover validation result from db
        try {
            JsonObject voteJsonObject = JsonUtils.getJsonObject(ballotBox.getVote());
            Vote vote = ObjectMappers.fromJson(voteJsonObject.getJsonObject(VOTE).toString(), Vote.class);
            String voteHash = voteHashService.hash(vote);
            voteValidationRepository.findByTenantIdElectionEventIdVotingCardId(ballotBox.getTenantId(),
                ballotBox.getElectionEventId(), ballotBox.getVotingCardId(), voteHash);
            LOG.info("All the vote validations are ok. Hash successfully retrieved from VOTE_VALIDATION.");
            secureLoggerWriter.log(Level.INFO,
                new LogContent.LogContentBuilder().logEvent(ElectionInformationLogEvents.VOTE_HASH_FOUND)
                    .objectId(ballotBox.getBallotBoxId()).user(ballotBox.getVotingCardId())
                    .electionEvent(ballotBox.getElectionEventId())
                    .additionalInfo(ElectionInformationLogConstants.INFO_HASH_VOTE, voteHash).createLogInfo());
            return true;
        } catch (ResourceNotFoundException e) {
            LOG.error("Vote hash not found in VOTE_VALIDATION. Invalid vote.", e);
            throw new ValidationException(null,
                "Invalid vote. The hash of the vote is not found at VOTE_VALIDATION table: "
                    + ExceptionUtils.getRootCauseMessage(e));
        } catch (GeneralCryptoLibException e) {
            throw new ValidationException(null,
                "Invalid vote. Error computing vote hash:" + ExceptionUtils.getRootCauseMessage(e));
        } catch (IOException e) {
            throw new ValidationException(null,
                "Invalid vote. Could not convert json to object:" + ExceptionUtils.getRootCauseMessage(e));
        }
    }
}
