/*
 * 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.model.rule;

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

import javax.json.JsonException;

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

import com.scytl.products.ov.commons.beans.domain.model.authentication.AuthenticationToken;
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.SemanticErrorException;
import com.scytl.products.ov.commons.beans.exceptions.SyntaxErrorException;
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.ui.ws.rs.ObjectMappers;
import com.scytl.products.ov.commons.util.ValidationUtils;
import com.scytl.products.ov.commons.validation.ValidationError;

/**
 * Rule to check the vote ids vs. the auth token ids.
 */
public class VoteIdsRule implements AbstractRule<Vote> {

    public static final String VOTER_INFORMATION_IS_NULL = "authToken.voterInformation is null";

    public static final String ERROR_CONVERTING_AUTH_TOKEN_JSON_TO_OBJECT =
        "Error converting auth token json to object";

    public static final String CREDENTIAL_ID_IN_VOTE_AND_AUTH_TOKEN_ARE_DIFFERENT =
        "Credential id in vote and auth token are different";

    public static final String BALLOT_ID_IN_VOTE_AND_AUTH_TOKEN_ARE_DIFFERENT =
        "Ballot id in vote and auth token are different";

    public static final String BALLOT_BOX_ID_IN_VOTE_AND_AUTH_TOKEN_ARE_DIFFERENT =
        "Ballot box id in vote and auth token are different";

    public static final String VOTING_CARD_ID_IN_VOTE_AND_AUTH_TOKEN_ARE_DIFFERENT =
        "Voting card id in vote and auth token are different";

    public static final String ELECTION_EVENT_ID_IN_VOTE_AND_AUTH_TOKEN_ARE_DIFFERENT =
        "Election event id in vote and auth token are different";

    public static final String TENANT_ID_IN_VOTE_AND_AUTH_TOKEN_ARE_DIFFERENT =
        "Tenant id in vote and auth token are different";

    public static final String VOTE_IS_NULL = "Vote is null";

    public static final String AUTH_TOKEN_IS_NULL = "Auth token is null";

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

    /**
     * @see com.scytl.products.ov.ei.services.domain.model.rule.VoteIdsRule#execute(
     *      com.scytl.products.ov.commons.beans.domain.model.vote.Vote)
     */
    @Override
    public ValidationError execute(Vote vote) {
        ValidationError result = new ValidationError();
        AuthenticationToken authToken = null;
        try {
            authToken =
                ObjectMappers.fromJson(vote.getAuthenticationToken(), AuthenticationToken.class);
            validateAuthToken(authToken);
        } catch (JsonException | IOException | SyntaxErrorException | SemanticErrorException | NullPointerException e) {
            LOG.error(ERROR_CONVERTING_AUTH_TOKEN_JSON_TO_OBJECT, e);
            result.setErrorArgs(new String[] {ERROR_CONVERTING_AUTH_TOKEN_JSON_TO_OBJECT
                + ExceptionUtils.getRootCauseMessage(e) });
            return result;
        }

        try {
            validateInputParams(vote, authToken);
        } catch (ApplicationException e) {
            LOG.error("Error trying to validate input params", e);
            result.setErrorArgs(new String[] {e.getMessage() });
            return result;
        }

        List<ValidationIdItem> validations = buildValidationRules(vote, authToken);
        return executeValidations(validations);
    }

    /**
     * Method for spy with mockito.
     * 
     * @param authToken
     *            the auth token.
     * @throws SyntaxErrorException
     * @throws SemanticErrorException
     */
    public void validateAuthToken(AuthenticationToken authToken) throws SyntaxErrorException, SemanticErrorException {
        ValidationUtils.validate(authToken);
    }

    // validate input params
    private void validateInputParams(Vote vote, AuthenticationToken authToken) throws ApplicationException {
        if (vote == null) {
            LOG.error(VOTE_IS_NULL);
            throw new ApplicationException(VOTE_IS_NULL);
        }

        if (authToken == null) {
            LOG.error(AUTH_TOKEN_IS_NULL);
            throw new ApplicationException(AUTH_TOKEN_IS_NULL);
        }

        if (authToken.getVoterInformation() == null) {
            LOG.error(VOTER_INFORMATION_IS_NULL);
            throw new ApplicationException(VOTER_INFORMATION_IS_NULL);
        }
    }

    // executes rules
    private ValidationError executeValidations(List<ValidationIdItem> validations) {
        ValidationError result = new ValidationError();
        result.setValidationErrorType(ValidationErrorType.SUCCESS);
        for (ValidationIdItem validation : validations) {
            if (!isValid(validation)) {
                result.setValidationErrorType(ValidationErrorType.FAILED);
                result.setErrorArgs(new String[] {validation.getErrorMessage() });
                break;
            }
        }
        return result;
    }

    // builds the validation rule list
    private List<ValidationIdItem> buildValidationRules(Vote vote, AuthenticationToken authToken) {
        List<ValidationIdItem> validations = new ArrayList<>();
        validations.add(new ValidationIdItem(vote.getTenantId(), authToken.getVoterInformation().getTenantId(),
            TENANT_ID_IN_VOTE_AND_AUTH_TOKEN_ARE_DIFFERENT));
        validations.add(new ValidationIdItem(vote.getElectionEventId(), authToken.getVoterInformation()
            .getElectionEventId(), ELECTION_EVENT_ID_IN_VOTE_AND_AUTH_TOKEN_ARE_DIFFERENT));
        validations.add(new ValidationIdItem(vote.getVotingCardId(), authToken.getVoterInformation().getVotingCardId(),
            VOTING_CARD_ID_IN_VOTE_AND_AUTH_TOKEN_ARE_DIFFERENT));
        validations.add(new ValidationIdItem(vote.getBallotBoxId(), authToken.getVoterInformation().getBallotBoxId(),
            BALLOT_BOX_ID_IN_VOTE_AND_AUTH_TOKEN_ARE_DIFFERENT));
        validations.add(new ValidationIdItem(vote.getBallotId(), authToken.getVoterInformation().getBallotId(),
            BALLOT_ID_IN_VOTE_AND_AUTH_TOKEN_ARE_DIFFERENT));
        validations.add(new ValidationIdItem(vote.getCredentialId(), authToken.getVoterInformation().getCredentialId(),
            CREDENTIAL_ID_IN_VOTE_AND_AUTH_TOKEN_ARE_DIFFERENT));
        return validations;
    }

    // applies validation rule
    private boolean isValid(ValidationIdItem validation) {
        boolean result = false;
        if (!isEmpty(validation.getId1()) && !isEmpty(validation.getId2())) {
            result = validation.getId1().equals(validation.getId2());
        }
        return result;
    }

    // checks for null
    private boolean isEmpty(String id) {
        return id == null || id.trim().isEmpty();
    }

    @Override
    public String getName() {
        return RuleNames.VOTE_IDS.getText();
    }
}
