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

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.json.Json;
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.Response.Status;
import javax.ws.rs.core.StreamingOutput;

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

import com.scytl.products.oscore.logging.core.provider.TransactionInfoProvider;
import com.scytl.products.ov.channel.cc.orchestrator.mixdec.domain.model.BallotBoxStatus;
import com.scytl.products.ov.channel.cc.orchestrator.mixdec.domain.services.MixDecBallotBoxService;
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.exceptions.ApplicationException;
import com.scytl.products.ov.commons.beans.exceptions.ApplicationExceptionMessages;
import com.scytl.products.ov.commons.beans.exceptions.LambdaException;
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;

@Path(MixDecBallotBoxResource.RESOURCE_PATH)
@Stateless(name = "or-MixDecBallotBoxResource")
public class MixDecBallotBoxResource {

    /* Base path to resource */
    static final String RESOURCE_PATH =
        "/mixdec/secured/tenant/{tenantId}/electionevent/{electionEventId}/ballotbox/{ballotBoxId}";

    static final String PATH_STATUS = "status";

    public static final String PATH_NODE_OUTPUTS = "nodeoutputs";

    private static final String PATH_PARAMETER_TENANT_ID = "tenantId";

    private static final String PATH_PARAMETER_ELECTION_EVENT_ID = "electionEventId";

    private static final String PATH_PARAMETER_BALLOT_BOX_ID = "ballotBoxId";

    @Inject
    private MixDecBallotBoxService mixDecBallotBoxService;

    @Inject
    private TrackIdInstance trackIdInstance;

    @Inject
    private TransactionInfoProvider transactionInfoProvider;

    @Inject
    private HttpRequestService httpRequestService;

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

    @GET
    @Path(PATH_STATUS)
    @Produces(MediaType.APPLICATION_JSON)
    public Response getBallotBoxMixingStatus(@HeaderParam(Constants.PARAMETER_HEADER_X_REQUEST_ID) String trackingId,
            @PathParam(PATH_PARAMETER_TENANT_ID) final String tenantId,
            @PathParam(PATH_PARAMETER_ELECTION_EVENT_ID) final String electionEventId,
            @PathParam(PATH_PARAMETER_BALLOT_BOX_ID) final String ballotBoxId,
            @Context final HttpServletRequest request)
            throws ApplicationException {

        trackIdInstance.setTrackId(trackingId);

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

        validateParameters(tenantId, electionEventId, ballotBoxId);

        LOG.info("Checking mixing status of the ballot box for tenant {} electionEventId {} and ballotBoxId {}",
            tenantId, electionEventId, ballotBoxId);

        List<BallotBoxStatus> mixDecBallotBoxStatus =
            mixDecBallotBoxService.getMixDecBallotBoxStatus(tenantId, electionEventId, new String[] {ballotBoxId });

        String result = Json.createObjectBuilder().add("status", mixDecBallotBoxStatus.get(0).getProcessStatus())
            .build().toString();

        return Response.ok().entity(result).build();
    }

    @GET
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
    @Deprecated // SV-5291
    public Response getProcessedBallotBox(@HeaderParam(Constants.PARAMETER_HEADER_X_REQUEST_ID) String trackingId,
            @PathParam(PATH_PARAMETER_TENANT_ID) String tenantId,
            @PathParam(PATH_PARAMETER_ELECTION_EVENT_ID) String electionEventId,
            @PathParam(PATH_PARAMETER_BALLOT_BOX_ID) String ballotBoxId, @Context HttpServletRequest request)
            throws ApplicationException {

        trackIdInstance.setTrackId(trackingId);

        validateParameters(tenantId, electionEventId, ballotBoxId);

        transactionInfoProvider.generate(tenantId, httpRequestService.getIpClientAddress(request),
            httpRequestService.getIpServer(request));

        LOG.info("Getting processed ballot box for ballotBoxId: {}, electionEventId: {}, and tenantId: {}.",
            ballotBoxId, electionEventId, tenantId);

        List<BallotBoxStatus> ballotBoxStatuses =
            mixDecBallotBoxService.getMixDecBallotBoxStatus(tenantId, electionEventId, new String[] {ballotBoxId });

        Response response;
        if (ballotBoxStatuses.isEmpty()) {
            LOG.warn("Missing mixed ballot box " + ballotBoxId);
            response = Response.status(Status.NOT_FOUND).build();
        } else if (BallotBoxStatus.STATUS_NOT_FOUND.equals(ballotBoxStatuses.get(0).getProcessStatus())
            || BallotBoxStatus.STATUS_CLOSED.equals(ballotBoxStatuses.get(0).getProcessStatus())
            || BallotBoxStatus.STATUS_NOT_CLOSED.equals(ballotBoxStatuses.get(0).getProcessStatus())) {

            LOG.warn("Ballot box {} is not mixed ({})", ballotBoxId, ballotBoxStatuses.get(0).getProcessStatus());
            response = Response.status(Status.NOT_FOUND).build();
        } else if (BallotBoxStatus.STATUS_MIXED.equals(ballotBoxStatuses.get(0).getProcessStatus())) {
            StreamingOutput entity = stream -> mixDecBallotBoxService.writeEncryptedBallotBox(stream, tenantId,
                electionEventId, ballotBoxId);
            response = Response.ok().entity(entity)
                .header("Content-Disposition", "attachment; filename=CleansedBallotBox.csv;").build();
        } else {
            String errorMessage = ballotBoxStatuses.get(0).getErrorMessage();
            LOG.error("Error in mixed ballot box {}: {}", ballotBoxId, errorMessage);
            response = Response.status(Status.INTERNAL_SERVER_ERROR).entity(errorMessage).build();
        }

        return response;
    }

    /**
     * Gets the node outputs for the specified ballot box.
     * 
     * @param trackingId
     *            an identifier for the current request
     * @param tenantId
     *            the tenant that owns the ballot box
     * @param electionEventId
     *            the election event the ballot box belongs to
     * @param ballotBoxId
     *            the identifier of the ballot box
     * @param request
     * @return a zip file with the payload JSONs
     * @throws ApplicationException
     */
    @GET
    @Path(PATH_NODE_OUTPUTS)
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
    public Response getNodeOutputs(@HeaderParam(Constants.PARAMETER_HEADER_X_REQUEST_ID) String trackingId,
            @PathParam(PATH_PARAMETER_TENANT_ID) String tenantId,
            @PathParam(PATH_PARAMETER_ELECTION_EVENT_ID) String electionEventId,
            @PathParam(PATH_PARAMETER_BALLOT_BOX_ID) String ballotBoxId, @Context HttpServletRequest request)
            throws ApplicationException {

        trackIdInstance.setTrackId(trackingId);

        validateParameters(tenantId, electionEventId, ballotBoxId);

        transactionInfoProvider.generate(tenantId, httpRequestService.getIpClientAddress(request),
            httpRequestService.getIpServer(request));

        BallotBoxId bbid = new BallotBoxIdImpl(tenantId, electionEventId, ballotBoxId);
        LOG.info("Getting node outputs for ballot box {}...", bbid);

        List<BallotBoxStatus> ballotBoxStatuses =
            mixDecBallotBoxService.getMixDecBallotBoxStatus(tenantId, electionEventId, new String[] {ballotBoxId });

        Response response;
        if (ballotBoxStatuses.isEmpty()) {
            LOG.warn("The status of ballot box {} is not available", bbid);
            response = Response.status(Status.NOT_FOUND).build();
        } else {
            if (BallotBoxStatus.STATUS_MIXED.equals(ballotBoxStatuses.get(0).getProcessStatus())) {
                LOG.info("Preparing the payload JSONs for ballot box {}...", bbid);
                response = createPayloadJsonResponse(bbid);
            } else {
                String errorMessage = ballotBoxStatuses.get(0).getErrorMessage();
                LOG.warn("Ballot box {} is not mixed. {}", bbid, errorMessage);
                response = Response.status(Status.CONFLICT).entity(errorMessage).build();
            }
        }

        return response;
    }

    /**
     * Creates the response containing the payloads as a zip file with JSON
     * files in it.
     * 
     * @param bbid
     *            the ballot box identifier
     * @return the response with the zip ready to be streamed.
     */
    private Response createPayloadJsonResponse(BallotBoxId bbid) {
        StreamingOutput entity = outputStream -> {
            // Build a zip with the JSONs as a file each.
            try (ZipOutputStream zip = new ZipOutputStream(new BufferedOutputStream(outputStream))) {
                // Get the payloads from the database
                mixDecBallotBoxService.getOutputPayloadsJson(bbid)
                    // Place them in a multipart response
                    .forEach(payloadJson -> {
                        try {
                            zip.putNextEntry(new ZipEntry(payloadJson.getFileName()));
                            zip.write(payloadJson.getContents().getBytes());
                            zip.closeEntry();
                        } catch (IOException e) {
                            throw new LambdaException(e);
                        }
                    });
            }
        };

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

    /**
     * Validates mixing and decryption parameters.
     */
    private static void validateParameters(String tenantId, String electionEventId, String ballotBoxId)
            throws ApplicationException {
        if ((null == tenantId) || tenantId.isEmpty()) {
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_QUERY_PARAMETER_IS_NULL,
                RESOURCE_PATH, ErrorCodes.MISSING_QUERY_PARAMETER, PATH_PARAMETER_TENANT_ID);
        }

        if ((null == electionEventId) || electionEventId.isEmpty()) {
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_QUERY_PARAMETER_IS_NULL,
                RESOURCE_PATH, ErrorCodes.MISSING_QUERY_PARAMETER, PATH_PARAMETER_ELECTION_EVENT_ID);
        }

        if ((null == ballotBoxId) || ballotBoxId.isEmpty()) {
            throw new ApplicationException(ApplicationExceptionMessages.EXCEPTION_MESSAGE_QUERY_PARAMETER_IS_NULL,
                RESOURCE_PATH, ErrorCodes.MISSING_QUERY_PARAMETER, "ballotBoxId");
        }
    }
}
