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

import java.io.IOException;
import java.io.Reader;

import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotNull;
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 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.oscore.logging.core.provider.TransactionInfoProvider;
import com.scytl.products.ov.commons.beans.domain.model.confirmation.TraceableConfirmationMessage;
import com.scytl.products.ov.commons.beans.exceptions.ApplicationException;
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.beans.validation.CryptographicOperationException;
import com.scytl.products.ov.commons.dto.CastCodeAndComputeResults;
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.vv.infrastructure.log.VoteVerificationLogConstants;
import com.scytl.products.ov.vv.infrastructure.log.VoteVerificationLogEvents;
import com.scytl.products.ov.vv.service.CastCodeService;

/**
 * Web service to retrieve cast codes.
 */
@Path(CastCodeResource.RESOURCE_PATH)
@Stateless(name = "vv-CastCodeResource")
public class CastCodeResource {

    /**
     * 
     */

    static final String RESOURCE_PATH = "/castcodes";

    static final String RETRIEVE_CAST_CODES_PATH =
        "/tenant/{tenantId}/electionevent/{electionEventId}/verificationcard/{verificationCardId}";

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

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

    // The name of the parameter value verification card id.
    static final String PARAMETER_VALUE_VERIFICATION_CARD_ID = "verificationCardId";

    // String which defines the resource.
    private static final String RESOURCE = "CAST_CODES";

    // The constant for the error code "mandantory.field".
    private static final String ERROR_CODE_MANDATORY_FIELD = "mandatory.field";

    // The constant for the message "Verification card id is null".
    private static final String VERIFIATION_CARD_ID_IS_NULL = "Verification card id is null";

    // The constant for the message "Election event id is null".
    private static final String ELECTION_EVENT_ID_IS_NULL = "Election event id is null";

    // The constant for the message "Tenant id is null".
    private static final String TENANT_ID_IS_NULL = "Tenant id is null";

    // The track id instance
    @Inject
    private TrackIdInstance tackIdInstance;

    // The service used to retrieve the cast code message
    @Inject
    private CastCodeService castCodeService;

    @Inject
    private TransactionInfoProvider transactionInfoProvider;

    @Inject
    private HttpRequestService httpRequestService;

    @Inject
    private SecureLoggingWriter secureLoggerWriter;

    /**
     * Retrieve a cast code taking into account a tenant, election event and
     * verification card for a given confirmation message.
     *
     * @param trackingId
     *            - the track id to be used for logging purposes.
     * @param tenantId
     *            - the tenant identifier.
     * @param electionEventId
     *            - the election event identifier.
     * @param verificationCardId
     *            - the verification card identifier.
     * @param confirmationMessageReader
     *            - the confirmation message to process.
     * @param request
     *            - the http servlet request.
     * @return The http response of execute the operation. HTTP status code 200
     *         if the request has succeed.
     * @throws ApplicationException
     *             if any input parameters is null or empty.
     * @throws SemanticErrorException
     *             If there are semantic errors in the data included in the body
     *             of the request..
     * @throws SyntaxErrorException
     *             If the URI is incorrect or if the body of the request has
     *             syntax errors (For instance, a missing field)..
     * @throws IOException
     * @throws ResourceNotFoundException
     * @throws CryptographicOperationException
     * @throws ClassNotFoundException
     */
    @Path(RETRIEVE_CAST_CODES_PATH)
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response retrieveCastCodes(@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_VERIFICATION_CARD_ID) String verificationCardId,
            @NotNull Reader confirmationMessageReader, @Context HttpServletRequest request)
            throws ApplicationException, SyntaxErrorException, SemanticErrorException, IOException,
            ResourceNotFoundException, CryptographicOperationException, ClassNotFoundException {
        // set the track id to be logged
        tackIdInstance.setTrackId(trackingId);

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

        // in case something from the input is not valid, it throws an
        // exception
        validateInput(tenantId, electionEventId, verificationCardId);

        // convert json to object
        TraceableConfirmationMessage traceableConfirmationMessage =
            ObjectMappers.fromJson(confirmationMessageReader, TraceableConfirmationMessage.class);

        // [SL] hashCm hash of the confirmation message
        String hashCm = traceableConfirmationMessage.getAuthenticationTokenSignature();

        // validate voter verification
        try {
            ValidationUtils.validate(traceableConfirmationMessage);
        } catch (SyntaxErrorException | SemanticErrorException e) {
            // [SL PVCCREQ-2] Medium
            // Partial Choice Codes consolidation -- Confirmation Message
            // validation has failed.
            secureLoggerWriter.log(Level.INFO,
                new LogContent.LogContentBuilder()
                    .logEvent(VoteVerificationLogEvents.FAILED_CONFIRMATION_MESSAGE_VALIDATION)
                    .additionalInfo("#hash_CM", hashCm).additionalInfo(VoteVerificationLogConstants.INFO_ERR_DESC,
                        "Confirmation Message validation has failed")
                    .createLogInfo());
            throw e;
        }

        CastCodeAndComputeResults castCodeMessage;
        castCodeMessage = castCodeService.retrieveCastCodeAndSignature(tenantId, electionEventId, verificationCardId,
            traceableConfirmationMessage);

        return Response.ok().entity(castCodeMessage).build();

    }

    // Validate input parameters.
    private void validateInput(String tenantId, String electionEventId, String verificationCardId)
            throws ApplicationException {
        if (tenantId == null) {
            throw new ApplicationException(TENANT_ID_IS_NULL, RESOURCE, ERROR_CODE_MANDATORY_FIELD,
                PARAMETER_VALUE_TENANT_ID);
        }
        if (electionEventId == null) {
            throw new ApplicationException(ELECTION_EVENT_ID_IS_NULL, RESOURCE, ERROR_CODE_MANDATORY_FIELD,
                PARAMETER_VALUE_ELECTION_EVENT_ID);
        }
        if (verificationCardId == null) {
            throw new ApplicationException(VERIFIATION_CARD_ID_IS_NULL, RESOURCE, ERROR_CODE_MANDATORY_FIELD,
                PARAMETER_VALUE_VERIFICATION_CARD_ID);
        }
    }
}
