/*
 * 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.vw.ui.ws.rs.operation;

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

import java.io.IOException;

import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.slf4j.Logger;

import com.google.gson.Gson;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.products.oscore.logging.api.domain.LogContent;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.oscore.logging.core.provider.TransactionInfoProvider;
import com.scytl.products.ov.commons.beans.exceptions.ApplicationException;
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.SemanticErrorException;
import com.scytl.products.ov.commons.beans.exceptions.SyntaxErrorException;
import com.scytl.products.ov.commons.tracking.TrackIdInstance;
import com.scytl.products.ov.commons.ui.Constants;
import com.scytl.products.ov.commons.ui.ws.rs.ObjectMappers;
import com.scytl.products.ov.commons.util.HttpRequestService;
import com.scytl.products.ov.commons.util.ValidationUtils;
import com.scytl.products.ov.commons.validation.ValidationResult;
import com.scytl.products.ov.vw.infrastructure.log.SecureLoggerInformation;
import com.scytl.products.ov.vw.infrastructure.log.VotingWorkflowLogConstants;
import com.scytl.products.ov.vw.infrastructure.log.VotingWorkflowLogEvents;
import com.scytl.products.ov.vw.services.domain.model.authentication.AuthenticationToken;
import com.scytl.products.ov.vw.services.domain.model.authentication.AuthenticationTokenService;
import com.scytl.products.ov.vw.services.domain.model.confirmation.ConfirmationInformation;
import com.scytl.products.ov.vw.services.domain.model.confirmation.VoteCastResult;
import com.scytl.products.ov.vw.services.domain.service.CastVoteService;

/**
 * The endpoint for confirmation messages
 */
@Path("/confirmations")
@Stateless(name = "vw-ConfirmationMessageResource")
public class ConfirmationMessageResource {

    // The authentication token parameter
    private static final String AUTHENTICATION_TOKEN = "authenticationToken";

    // The name of the parameter value tenant id.
    private static final String PARAMETER_VALUE_TENANT_ID = "tenantId";

    // The name of the parameter value voting card id.
    private static final String PARAMETER_VALUE_VOTING_CARD_ID = "votingCardId";

    // The name of the parameter value election event id.
    private static final String PARAMETER_VALUE_ELECTION_EVENT_ID = "electionEventId";

    @Inject
    private TrackIdInstance trackIdInstance;

    @Inject
    private AuthenticationTokenService authenticationTokenService;

    @Inject
    private TransactionInfoProvider transactionInfoProvider;

    @Inject
    private HttpRequestService httpRequestService;

    @Inject
    private SecureLoggingWriter secureLoggerWriter;

    @Inject
    private SecureLoggerInformation secureLoggerInformation;

    @Inject
    private Logger logger;

    @Inject
    private CastVoteService castVoteService;

    /**
     * Receives a confirmation message with additional information , and validates this information.
     *
     * @param tenantId                      - Tenant identifier
     * @param electionEventId               - election event identifier
     * @param votingCardId                  - voting card identifier
     * @param confirmationInformation       - confirmation information to be validated
     * @param authenticationTokenJsonString - the authentication token
     * @param request                       - the http servlet request.
     * @return the result of the validation
     * @throws ApplicationException      if there are validations errors in input parameters.
     * @throws IOException               if there are problems during convention to json format.
     * @throws GeneralCryptoLibException if there are some problem during requestId generation.
     * @throws ResourceNotFoundException if ballot if not found.
     * @throws SemanticErrorException    if the authentication token or vote has semantic errors.
     * @throws SyntaxErrorException      if the authentication token or vote has syntax errors.
     * @throws DuplicateEntryException   if there is a fail during voting card state update
     */
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/tenant/{tenantId}/electionevent/{electionEventId}/votingcard/{votingCardId}")
    public Response validateConfirmationMessage(@HeaderParam(Constants.PARAMETER_HEADER_X_REQUEST_ID) String trackingId,
                                                @PathParam(PARAMETER_VALUE_TENANT_ID) String tenantId,
                                                @PathParam(PARAMETER_VALUE_ELECTION_EVENT_ID) String electionEventId,
                                                @PathParam(PARAMETER_VALUE_VOTING_CARD_ID) String votingCardId,
                                                ConfirmationInformation confirmationInformation,
                                                @HeaderParam(AUTHENTICATION_TOKEN) String authenticationTokenJsonString,
                                                @Context HttpServletRequest request)
        throws GeneralCryptoLibException, IOException, ResourceNotFoundException, ApplicationException, DuplicateEntryException {

        // Tracking id used for logging and tracking purposes
        trackIdInstance.setTrackId(trackingId);

        // transaction id generation
        transactionInfoProvider.generate(tenantId, httpRequestService.getIpClientAddress(request),
            httpRequestService.getIpServer(request));

        AuthenticationToken authenticationToken;
        ValidationResult authTokenValidationResult;
        try {
            // convert from json to object
            authenticationToken = ObjectMappers.fromJson(authenticationTokenJsonString,
                AuthenticationToken.class);

            // validate auth token
            ValidationUtils.validate(authenticationToken);
            // returns the result of authentication token validation
            authTokenValidationResult = authenticationTokenService.validateAuthenticationToken(tenantId,
                electionEventId, votingCardId, authenticationTokenJsonString);
        } catch (IOException | IllegalArgumentException | SyntaxErrorException | SemanticErrorException e) {
            logger.error("Invalid authentication token format.", e);
            return Response.status(Response.Status.UNAUTHORIZED).build();
        }

        // data to be logged
        secureLoggerInformation.setAuthenticationToken(authenticationTokenJsonString);
        secureLoggerInformation.setAuthenticationTokenObject(authenticationToken);
        secureLoggerInformation.setConfirmationInformation(confirmationInformation);

        // no need to continue if auth token is invalid
        if (!authTokenValidationResult.isResult()) {
            secureLoggerWriter.log(INFO,
                new LogContent.LogContentBuilder().logEvent(VotingWorkflowLogEvents.STOP_VOTING_PROCESS)
                    .objectId(secureLoggerInformation.getAuthenticationTokenHash()).user(votingCardId)
                    .electionEvent(electionEventId)
                    .additionalInfo(VotingWorkflowLogConstants.INFO_TRACK_ID, trackIdInstance.getTrackId())
                    .additionalInfo(VotingWorkflowLogConstants.INFO_ERR_DESC, "Error validating authentication token")
                    .createLogInfo());
            return getResponseInvalidToken(authTokenValidationResult);
        }

        // result of process
        VoteCastResult voteCastResult =
            castVoteService.castVote(authenticationTokenJsonString,
                authenticationToken.getVoterInformation(),
                confirmationInformation);
        return Response.ok().entity(voteCastResult).build();
    }

    /**
     * Constructs the response given a invalid validation result
     *
     * @param validationResult
     * @return
     */
    private Response getResponseInvalidToken(ValidationResult validationResult) {
        Gson gson = new Gson();
        String json = gson.toJson(validationResult);
        return Response.status(Response.Status.UNAUTHORIZED).entity(json).build();
    }
}
