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

import com.scytl.products.oscore.logging.core.provider.TransactionInfoProvider;
import com.scytl.products.ov.commons.beans.domain.model.ballotbox.BallotBoxId;
import com.scytl.products.ov.commons.beans.domain.model.ballotbox.BallotBoxIdImpl;
import com.scytl.products.ov.commons.beans.domain.model.messaging.PayloadSignatureException;
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.CleansedBallotBoxServiceException;
import com.scytl.products.ov.commons.beans.exceptions.LambdaException;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.beans.validation.ValidationErrorType;
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.ErrorCodes;
import com.scytl.products.ov.commons.util.HttpRequestService;
import com.scytl.products.ov.commons.validation.ValidationError;
import com.scytl.products.ov.ei.services.domain.model.validation.ElectionValidationRequest;
import com.scytl.products.ov.ei.services.domain.service.ballotbox.BallotBoxService;
import com.scytl.products.ov.ei.services.domain.service.ballotbox.CleansedBallotBoxService;
import com.scytl.products.ov.ei.services.domain.service.election.ElectionService;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
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.StreamingOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Path(CleansedBallotBoxResource.RESOURCE_NAME)
@Stateless
public class CleansedBallotBoxResource {

    // The name of the resource handle by this web service.
    static final String RESOURCE_NAME = "cleansedballotboxes";

    static final String CLEANSED_BALLOT_BOX_PATH =
        "tenant/{tenantId}/electionevent/{electionEventId}/ballotbox/{ballotBoxId}";

    static final String MIXING_PAYLOADS_PATH = CLEANSED_BALLOT_BOX_PATH + "/payloads";

    // The name of the query parameter tenantId
    private static final String QUERY_PARAMETER_TENANT_ID = "tenantId";

    // The name of the query parameter electionEventId
    private static final String QUERY_PARAMETER_ELECTION_EVENT_ID = "electionEventId";

    // The name of the query parameter ballotBoxId
    private static final String QUERY_PARAMETER_BALLOT_BOX_ID = "ballotBoxId";

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

    @Inject
    private CleansedBallotBoxService cleansedBallotBoxService;

    @Inject
    private BallotBoxService ballotBoxService;

    @Inject
    private ElectionService electionService;

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

    @Inject
    private TransactionInfoProvider transactionInfoProvider;

    @Inject
    private HttpRequestService httpRequestService;

    /**
     * Return a cleansed and encrypted ballot box given the tenant, the election
     * event and the ballot box identifiers.
     *
     * @param tenantId
     *            - the tenant identifier.
     * @param electionEventId
     *            - the election event identifier.
     * @param ballotBoxId
     *            - the ballot identifier.
     * @return Returns the corresponding ballot box for the tenantId,
     *         electionEventId and ballotBoxId.
     * @throws ApplicationException
     *             if the input parameters are not valid.
     */
    @GET
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
    @Path(CLEANSED_BALLOT_BOX_PATH)
    public Response getCleansedBallotBox(@HeaderParam(Constants.PARAMETER_HEADER_X_REQUEST_ID) final String trackingId,
            @PathParam(QUERY_PARAMETER_TENANT_ID) final String tenantId,
            @PathParam(QUERY_PARAMETER_ELECTION_EVENT_ID) final String electionEventId,
            @PathParam(QUERY_PARAMETER_BALLOT_BOX_ID) final String ballotBoxId,
            @Context final HttpServletRequest request) throws ApplicationException {

        trackIdInstance.setTrackId(trackingId);

        // validate parameters
        validateParameters(tenantId, electionEventId, ballotBoxId);

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

        try {

            boolean test = ballotBoxService.checkIfTest(tenantId, electionEventId, ballotBoxId);
            if (!test) {
                // The grace period will be included in the validation of the
                // election dates
                ElectionValidationRequest electionValidationRequest =
                    ElectionValidationRequest.create(tenantId, electionEventId, ballotBoxId, true);
                final ValidationError validationResult =
                    electionService.validateIfElectionIsOpen(electionValidationRequest);

                if (!ValidationErrorType.ELECTION_OVER_DATE.equals(validationResult.getValidationErrorType())) {
                    LOG.info("Ballot box not closed therefore not downloadable " + ballotBoxId);
                    return Response.status(Response.Status.PRECONDITION_FAILED).build();
                }
            }

        } catch (ResourceNotFoundException e) {
            LOG.error("Trying to download unknown ballot box {}", ballotBoxId, e);
            return Response.status(Response.Status.NOT_FOUND).build();
        }

        StreamingOutput entity =
            stream -> cleansedBallotBoxService.writeEncryptedBallotBox(stream, tenantId, electionEventId, ballotBoxId);

        return Response.ok().entity(entity).header("Content-Disposition", "attachment; filename=CleansedBallotBox.csv;")
            .build();
    }

    /**
     * Produces signed mixing payloads from a ballot box, ready to be sent to the mixing control
     * component's nodes.
     *
     * @param tenantId the tenant identifier.
     * @param electionEventId the election event identifier.
     * @param ballotBoxId the ballot identifier.
     * @return an object input stream with mixing payloads.
     * @throws ApplicationException if the input parameters are not valid.
     */
    @GET
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
    @Path(MIXING_PAYLOADS_PATH)
    public Response getMixingPayloads(@HeaderParam(Constants.PARAMETER_HEADER_X_REQUEST_ID) final String trackingId,
            @PathParam(QUERY_PARAMETER_TENANT_ID) final String tenantId,
            @PathParam(QUERY_PARAMETER_ELECTION_EVENT_ID) final String electionEventId,
            @PathParam(QUERY_PARAMETER_BALLOT_BOX_ID) final String ballotBoxId,
            @Context final HttpServletRequest request)
            throws ApplicationException, PayloadSignatureException, ResourceNotFoundException {

        trackIdInstance.setTrackId(trackingId);

        // validate parameters
        validateParameters(tenantId, electionEventId, ballotBoxId);

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

        try {

            if (!ballotBoxService.checkIfTest(tenantId, electionEventId, ballotBoxId)) {
                // The grace period will be included in the validation of the
                // election dates
                ElectionValidationRequest electionValidationRequest =
                        ElectionValidationRequest.create(tenantId, electionEventId, ballotBoxId, true);
                final ValidationError validationResult =
                        electionService.validateIfElectionIsOpen(electionValidationRequest);

                if (!ValidationErrorType.ELECTION_OVER_DATE.equals(validationResult.getValidationErrorType())) {
                    LOG.info("Ballot box not closed therefore not downloadable " + ballotBoxId);
                    return Response.status(Response.Status.PRECONDITION_FAILED).build();
                }
            }

        } catch (ResourceNotFoundException e) {
            LOG.error("Trying to download unknown ballot box " + ballotBoxId, e);
            return Response.status(Response.Status.NOT_FOUND).build();
        }

        BallotBoxId bbid = new BallotBoxIdImpl(tenantId, electionEventId, ballotBoxId);

        // Write each of the payloads into the stream.
        StreamingOutput streamingOutput = os -> {
            try (ObjectOutput oo = new ObjectOutputStream(os)) {
                cleansedBallotBoxService.getMixingPayloads(bbid).forEach(payload -> {
                    try {
                        LOG.info("Sending payload #{}...", payload.getVoteSetId().getIndex());
                        oo.writeObject(payload);
                    } catch (IOException e) {
                        throw new LambdaException(e);
                    }
                });
                LOG.info("All payloads from ballot box {} are now sent", bbid);
            } catch (PayloadSignatureException | ResourceNotFoundException| CleansedBallotBoxServiceException e) {
                throw new LambdaException(e);
            }
        };

        return Response.ok().entity(streamingOutput).build();
    }
    // Validate parameters.
    private void validateParameters(final String tenantId, final String electionEventId, final String ballotBoxId)
            throws ApplicationException {
        if (tenantId == null || tenantId.isEmpty())
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_QUERY_PARAMETER_IS_NULL,
                RESOURCE_NAME, ErrorCodes.MISSING_QUERY_PARAMETER, QUERY_PARAMETER_TENANT_ID);

        if (electionEventId == null || electionEventId.isEmpty()) {
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_QUERY_PARAMETER_IS_NULL,
                RESOURCE_NAME, ErrorCodes.MISSING_QUERY_PARAMETER, QUERY_PARAMETER_ELECTION_EVENT_ID);
        }
        if (ballotBoxId == null || ballotBoxId.isEmpty()) {
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_QUERY_PARAMETER_IS_NULL,
                RESOURCE_NAME, ErrorCodes.MISSING_QUERY_PARAMETER, QUERY_PARAMETER_BALLOT_BOX_ID);
        }

    }
}
