/*
 * 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
 */
/**
 * Created by riglesias on 16/03/15.
 */
package com.scytl.products.ov.ei.ui.ws.rs.application.execution;

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.GET;
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 javax.ws.rs.core.Response.Status;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.scytl.products.oscore.logging.core.provider.TransactionInfoProvider;
import com.scytl.products.ov.commons.beans.domain.model.receipt.Receipt;
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.dto.VoteAndComputeResults;
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.ui.ws.rs.persistence.ErrorCodes;
import com.scytl.products.ov.commons.ui.ws.rs.persistence.Message;
import com.scytl.products.ov.commons.util.HttpRequestService;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBoxRepository;
import com.scytl.products.ov.ei.services.domain.service.vote.VoteService;

/**
 * Web Service which receives a vote already validated and tries to store it in
 * the ballot box.
 */
@Path("/votes")
@Stateless
public class VoteResource {

    // 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";

    // The name of the parameter value authentication token.
    private static final String PARAMETER_AUTHENTICATION_TOKEN = "authenticationToken";

    private final Gson gson = new Gson();

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

    @Inject
    private VoteService voteService;

    // An instance of the ballot box repository
    @Inject

    private BallotBoxRepository ballotBoxRepository;

    @Inject
    private TransactionInfoProvider transactionInfoProvider;

    @Inject
    private HttpRequestService httpRequestService;

    private static final Logger LOG = LoggerFactory.getLogger(VoteResource.class);

    /**
     * Saves the content of a vote and returns the generated receipt.
     *
     * @param trackingId
     *            - the track id to be used for logging purposes.
     * @param electionEventId
     *            - election event identifier
     * @param tenantId
     *            - tenant identifier
     * @param voteInput
     *            - the vote to be stored.
     * @param authenticationToken
     *            - the authentication token.
     * @param request
     *            - the http servlet request.
     * @return the result of saving the vote with the receipt.
     * @throws ApplicationException
     *             if there was an error generating the receipt
     * @throws IOException
     *             if there are errors during conversion of vote to json format.
     * @throws ValidationException
     *             if vote is not valid.
     * @throws ResourceNotFoundException
     *             basic information needed to process this request could not be
     *             found
     */
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/tenant/{tenantId}/electionevent/{electionEventId}")
    public Response saveVote(@HeaderParam(Constants.PARAMETER_HEADER_X_REQUEST_ID) String trackingId,
            @PathParam(PARAMETER_VALUE_TENANT_ID) String tenantId,
            @PathParam(PARAMETER_VALUE_ELECTION_EVENT_ID) String electionEventId, @NotNull Reader voteInput,
            @NotNull @HeaderParam(PARAMETER_AUTHENTICATION_TOKEN) String authenticationToken,
            @Context HttpServletRequest request)
            throws ApplicationException, IOException, ValidationException, ResourceNotFoundException {

        // set the track id to be logged
        trackIdInstance.setTrackId(trackingId);

        // transform the json to object
        VoteAndComputeResults vote = ObjectMappers.fromJson(voteInput, VoteAndComputeResults.class);

        // transaction id generation
        transactionInfoProvider.generate(vote.getVote().getTenantId(), httpRequestService.getIpClientAddress(request),
            httpRequestService.getIpServer(request));

        try {

            Receipt receipt = voteService.saveVoteGenerateReceipt(vote, authenticationToken);
            // return the receipt
            return Response.ok().entity(receipt).build();

        } catch (DuplicateEntryException e) {
            LOG.error("Error storing the vote for tenant: {} and ballot: {} into ballotBox: {}.",
                vote.getVote().getTenantId(), vote.getVote().getBallotId(), vote.getVote().getBallotBoxId(), e);
            String errorCode = ErrorCodes.DUPLICATE_ENTRY;
            Message message = new Message();
            message.setText("Duplicated vote entry");
            message.addError("", "", errorCode);
            return Response.status(Status.CONFLICT).entity(message).build();
        }

    }

    /**
     * Returns a Vote object which matches with the provided parameters.
     *
     * @param trackingId
     *            - the track id to be used for logging purposes.
     * @param tenantId
     *            - the tenant identifier.
     * @param electionEventId
     *            - the election event identifier.
     * @param votingCardId
     *            - the voting card identifier.
     * @param request
     *            - the http servlet request.
     * @return If the operation is successfully performed, returns a response
     *         with HTTP status code 200 and the Vote in json format.
     * @throws ApplicationException
     *             if one of the input parameters is not valid.
     * @throws ResourceNotFoundException
     *             if there is no vote found.
     */
    @Path("/tenant/{tenantId}/electionevent/{electionEventId}/votingcard/{votingCardId}")
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getVote(@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, @Context HttpServletRequest request)
            throws ApplicationException, ResourceNotFoundException {
        // set the track id to be logged
        trackIdInstance.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, votingCardId);

        // return json
        VoteAndComputeResults voteWithComputeResults =
            voteService.retrieveVote(tenantId, electionEventId, votingCardId);

        // response
        return Response.ok(voteWithComputeResults).build();
    }

    /**
     * Checks is a vote exists for the specified parameters
     *
     * @param trackingId
     *            - the track id to be used for logging purposes.
     * @param tenantId
     *            - the tenant identifier.
     * @param electionEventId
     *            - the election event identifier.
     * @param votingCardId
     *            - the voting card identifier.
     * @param request
     *            - the http servlet request.
     * @return 200 if the votes exists, 404 if not
     * @throws ApplicationException
     *             if one of the input parameters is not valid.
     */
    @Path("/tenant/{tenantId}/electionevent/{electionEventId}/votingcard/{votingCardId}/available")
    @GET
    public Response checkVote(@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, @Context HttpServletRequest request)
            throws ApplicationException {
        try {
            getVote(trackingId, tenantId, electionEventId, votingCardId, request);
            return Response.ok().build();
        } catch (ResourceNotFoundException e) {
            LOG.info("Resource not found trying to check vote.", e);
            return Response.status(Status.NOT_FOUND).build();
        }
    }

    // Does a basic validation of the input. In case something is wrong, just
    // throws an exception.
    private void validateInput(String tenantId, String electionEventId, String votingCardId)
            throws ApplicationException {
        if (tenantId == null || tenantId.isEmpty()) {
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_TENANT_ID_IS_NULL);
        }
        if (electionEventId == null || electionEventId.isEmpty()) {
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_ELECTION_EVENT_ID_IS_NULL);
        }
        if (votingCardId == null || votingCardId.isEmpty()) {
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_VOTING_CARD_ID_IS_NULL);
        }
    }

}
