/*
 * 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.infrastructure.persistence;

import static com.scytl.products.oscore.logging.api.domain.Level.ERROR;
import static com.scytl.products.oscore.logging.api.domain.Level.INFO;

import java.io.IOException;
import java.util.List;

import javax.decorator.Decorator;
import javax.decorator.Delegate;
import javax.inject.Inject;
import javax.json.JsonObject;
import javax.persistence.PersistenceException;

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

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.receipt.Receipt;
import com.scytl.products.ov.commons.beans.domain.model.vote.Vote;
import com.scytl.products.ov.commons.beans.exceptions.DuplicateEntryException;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.logging.config.Audit;
import com.scytl.products.ov.commons.tracking.TrackIdInstance;
import com.scytl.products.ov.commons.ui.ws.rs.ObjectMappers;
import com.scytl.products.ov.commons.util.JsonUtils;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBox;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBoxRepository;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.ExportedBallotBoxItem;
import com.scytl.products.ov.ei.services.infrastructure.log.ElectionInformationLogConstants;
import com.scytl.products.ov.ei.services.infrastructure.log.ElectionInformationLogEvents;
import com.scytl.products.ov.ei.services.infrastructure.log.SecureLoggerHelper;

/**
 * Decorator of the ballot box repository.
 */
@Decorator
public abstract class BallotBoxRepositoryDecorator implements BallotBoxRepository {

    private static final String VOTE = "vote";

    private static final String RECEIPT = "receipt";

    @Inject
    @Delegate
    private BallotBoxRepository ballotBoxRepository;

    @Inject
    private TrackIdInstance trackId;

    @Inject
    private SecureLoggingWriter secureLoggerWriter;

    @Inject
    @Audit
    private SecureLoggingWriter auditLoggerWriter;

    @Inject
    private SecureLoggerHelper secureLoggerHelper;

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

    @Override
    public BallotBox save(BallotBox ballotBox) throws DuplicateEntryException {
        JsonObject voteJsonObject = JsonUtils.getJsonObject(ballotBox.getVote());
        Vote vote = new Vote();
        Receipt receipt = new Receipt();
        try {
            vote = ObjectMappers.fromJson(voteJsonObject.get(VOTE).toString(), Vote.class);
            receipt = ObjectMappers.fromJson(voteJsonObject.get(RECEIPT).toString(), Receipt.class);
        } catch (IOException e) {
            LOG.error("Error processing the vote or the receipt.", e);
        }
        String authTokenHash = secureLoggerHelper.getAuthTokenHash(vote.getAuthenticationToken());
        String voteHash = secureLoggerHelper.getVoteHash(vote);

        try {
            // call to delegate
            BallotBox result = ballotBoxRepository.save(ballotBox);
            LogContent saveLogInfo =
                new LogContent.LogContentBuilder().logEvent(ElectionInformationLogEvents.VOTE_CORRECTLY_STORED)
                    .objectId(authTokenHash).user(vote.getVotingCardId()).electionEvent(vote.getElectionEventId())
                    .additionalInfo(ElectionInformationLogConstants.INFO_TRACK_ID, trackId.getTrackId())
                    .additionalInfo(ElectionInformationLogConstants.INFO_HASH_VOTE, voteHash)
                    .additionalInfo(ElectionInformationLogConstants.INFO_RECEIPT, receipt.getReceipt())
                    .additionalInfo(ElectionInformationLogConstants.INFO_BALLOT_BOX_ID, vote.getBallotBoxId())
                    .createLogInfo();
            secureLoggerWriter.log(INFO, saveLogInfo);
            auditLoggerWriter.log(INFO, saveLogInfo);
            return result;
        } catch (PersistenceException | DuplicateEntryException e) {
            LogContent errorSavingLogInfo =
                new LogContent.LogContentBuilder().logEvent(ElectionInformationLogEvents.ERROR_SAVING_VOTE)
                    .objectId(authTokenHash).user(vote.getVotingCardId()).electionEvent(vote.getElectionEventId())
                    .additionalInfo(ElectionInformationLogConstants.INFO_TRACK_ID, trackId.getTrackId())
                    .additionalInfo(ElectionInformationLogConstants.INFO_HASH_VOTE, voteHash)
                    .additionalInfo(ElectionInformationLogConstants.INFO_RECEIPT, receipt.getReceipt())
                    .additionalInfo(ElectionInformationLogConstants.INFO_ERR_DESC,
                        "Error saving the vote and receipt: " + ExceptionUtils.getRootCauseMessage(e))
                    .createLogInfo();
            secureLoggerWriter.log(ERROR, errorSavingLogInfo);
            auditLoggerWriter.log(ERROR, errorSavingLogInfo);
            throw e;
        }
    }

    /**
     * @see com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBoxRepository#findByTenantIdElectionEventIdVotingCardId(java.lang.String,
     *      java.lang.String, java.lang.String)
     */
    @Override
    public BallotBox findByTenantIdElectionEventIdVotingCardId(String tenantId, String electionEventId,
            String votingCardId) throws ResourceNotFoundException {
        try {
            BallotBox result =
                ballotBoxRepository.findByTenantIdElectionEventIdVotingCardId(tenantId, electionEventId, votingCardId);
            secureLoggerWriter.log(INFO,
                new LogContent.LogContentBuilder().logEvent(ElectionInformationLogEvents.VOTE_FOUND)
                    .objectId(votingCardId).user(votingCardId).electionEvent(electionEventId)
                    .additionalInfo(ElectionInformationLogConstants.INFO_TRACK_ID, trackId.getTrackId())
                    .createLogInfo());
            return result;
        } catch (ResourceNotFoundException e) {
            secureLoggerWriter.log(ERROR,
                new LogContent.LogContentBuilder().logEvent(ElectionInformationLogEvents.VOTE_NOT_FOUND)
                    .objectId(votingCardId).user(votingCardId).electionEvent(electionEventId)
                    .additionalInfo(ElectionInformationLogConstants.INFO_TRACK_ID, trackId.getTrackId())
                    .additionalInfo(ElectionInformationLogConstants.INFO_ERR_DESC,
                        "Resource not found in the BALLOT_BOX table.")
                    .createLogInfo());
            throw e;
        }
    }

    @Override
    public List<ExportedBallotBoxItem> getEncryptedVotesByTenantIdElectionEventIdBallotBoxId(final String tenantId,
            final String electionEventId, final String ballotBoxId, final int firstElement, final int lastElement) {
        List<ExportedBallotBoxItem> result = ballotBoxRepository.getEncryptedVotesByTenantIdElectionEventIdBallotBoxId(
            tenantId, electionEventId, ballotBoxId, firstElement, lastElement);
        secureLoggerWriter.log(INFO,
            new LogContent.LogContentBuilder().logEvent(ElectionInformationLogEvents.ENCRYPTED_VOTES_FOUND)
                .objectId(ballotBoxId).user(ballotBoxId).electionEvent(electionEventId)
                .additionalInfo(ElectionInformationLogConstants.INFO_TRACK_ID, trackId.getTrackId())
                .additionalInfo(ElectionInformationLogConstants.FIRST_ELEMENT, String.valueOf(firstElement))
                .additionalInfo(ElectionInformationLogConstants.LAST_ELEMENT, String.valueOf(lastElement))
                .createLogInfo());
        return result;
    }

    /**
     * @see com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBoxRepository#findByTenantIdElectionEventIdBallotBoxId(java.lang.String,
     *      java.lang.String, java.lang.String)
     */
    @Override
    public List<BallotBox> findByTenantIdElectionEventIdBallotBoxId(String tenantId, String electionEventId,
            String ballotBoxId) {
        return ballotBoxRepository.findByTenantIdElectionEventIdBallotBoxId(tenantId, electionEventId, ballotBoxId);
    }

}
