/*
 * 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 java.io.IOException;
import java.util.List;

import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotNull;
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 javax.ws.rs.core.StreamingOutput;

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

import com.google.gson.Gson;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.products.oscore.logging.core.provider.TransactionInfoProvider;
import com.scytl.products.ov.commons.beans.domain.model.EntityId;
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.EntryPersistenceException;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.beans.validation.CryptographicOperationException;
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.util.JsonUtils;
import com.scytl.products.ov.commons.validation.ValidationError;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBoxInformation;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBoxInformationRepository;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBoxRepository;
import com.scytl.products.ov.ei.services.domain.model.content.ElectoralAuthorityEntity;
import com.scytl.products.ov.ei.services.domain.model.content.ElectoralAuthorityRepository;
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.election.ElectionService;

/**
 * Web service for handling ballot box information.
 */
@Path("/ballotboxes")
@Stateless
public class BallotBoxResource {

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

    // 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 name of the json parameter electoralAuthorityId
    private static final String JSON_PARAMETER_ELECTORAL_AUTHORITY_ID = "electoralAuthorityId";

    // The name of the json structure electoralAuthority
    private static final String JSON_NAME_ELECTORAL_AUTHORITY = "electoralAuthority";

    private static final String JSON_PARAMETER_SIGNATURE = "signature";

    private static final String JSON_PARAMETER_ISBLOCKED = "isblocked";

    private final Gson gson = new Gson();

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

    @EJB
    private BallotBoxInformationRepository ballotBoxInformationRepository;

    @EJB
    private ElectoralAuthorityRepository electoralAuthorityRepository;

    @EJB
    private BallotBoxRepository ballotBoxRepository;

    @Inject
    private BallotBoxService ballotBoxService;

    @Inject
    private ElectionService electionService;

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

    @Inject
    private TransactionInfoProvider transactionInfoProvider;

    @Inject
    private HttpRequestService httpRequestService;

    /**
     * Return a ballot box information given the tenant, the election event and
     * the ballot identifiers.
     *
     * @param tenantId
     *            - the tenant identifier.
     * @param electionEventId
     *            - the election event identifier.
     * @param ballotBoxId
     *            - the ballot identifier.
     * @param trackingId
     *            - the track id to be used for logging purposes.
     * @param request
     *            - the http servlet request.
     * @return Returns the corresponding ballot information for the tenantId,
     *         electionEventId and ballotId.
     * @throws ApplicationException
     *             if the input parameters are not valid.
     * @throws IOException
     *             if there are errors during conversion of ballot to json
     *             format.
     * @throws ResourceNotFoundException
     *             if the ballot is not found.
     */
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("tenant/{tenantId}/electionevent/{electionEventId}/ballotbox/{ballotBoxId}")
    public Response getBallotBoxInformation(@HeaderParam(Constants.PARAMETER_HEADER_X_REQUEST_ID) 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, IOException, ResourceNotFoundException {

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

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

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

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

        final BallotBoxInformation ballotBoxInformation = ballotBoxInformationRepository
            .findByTenantIdElectionEventIdBallotBoxId(tenantId, electionEventId, ballotBoxId);

        LOG.info("Ballot box information for ballotBoxId: {}, electionEventId: {}, and tenantId: {} found.",
            ballotBoxId, electionEventId, tenantId);

        final JsonObject bbInfoJsonObject = JsonUtils.getJsonObject(ballotBoxInformation.getJson());

        final String electoralAuthorityId = bbInfoJsonObject.getString(JSON_PARAMETER_ELECTORAL_AUTHORITY_ID);

        // search electoral authority information
        LOG.info(
            "Getting electoral authority information for electoral authority: {}, electionEventId: {}, and tenantId: {}.",
            electoralAuthorityId, electionEventId, tenantId);

        final ElectoralAuthorityEntity electoralAuthority = electoralAuthorityRepository
            .findByTenantIdElectionEventIdElectoralAuthorityId(tenantId, electionEventId, electoralAuthorityId);

        LOG.info("Electoral authority for electoralAuthorityId: {}, electionEventId: {}, and tenantId: {} found.",
            electoralAuthorityId, electionEventId, tenantId);

        final JsonObject electoralAuthorityJson = JsonUtils.getJsonObject(electoralAuthority.getJson());

        // enrich bb info json
        final JsonObjectBuilder bbInfoWithEAInfoJsonObjectBuilder =
            JsonUtils.jsonObjectToBuilder(bbInfoJsonObject).add(JSON_NAME_ELECTORAL_AUTHORITY, electoralAuthorityJson);

        // add signature
        JsonObject bbInfoWithEAInfoJsonObject = bbInfoWithEAInfoJsonObjectBuilder
            .add(JSON_PARAMETER_SIGNATURE, ballotBoxInformation.getSignature()).build();

        final String bbInfoWithEAInfo = bbInfoWithEAInfoJsonObject.toString();

        // convert to string
        final String bbInfoWithEAInfoJson = gson.toJson(bbInfoWithEAInfo);

        // send json value of ballot information enriched with the electoral
        // authority json
        return Response.ok().entity(bbInfoWithEAInfoJson).build();
    }

    /**
     * Return an 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 ResourceNotFoundException
     *             if the ballot box is not found.
     * @throws ApplicationException
     *             if the input parameters are not valid.
     * @throws CryptographicOperationException
     * @throws GeneralCryptoLibException
     */
    @GET
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM })
    @Path("secured/tenant/{tenantId}/electionevent/{electionEventId}/ballotbox/{ballotBoxId}")
    public Response getEncryptedBallotBoxes(
            @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 ResourceNotFoundException, ApplicationException, IOException, CryptographicOperationException,
            GeneralCryptoLibException {

        trackIdInstance.setTrackId(trackingId);

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

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

        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())) {
                return Response.status(Status.PRECONDITION_FAILED).build();
            }
        }

        StreamingOutput entity =
            stream -> ballotBoxService.writeEncryptedBallotBox(stream, tenantId, electionEventId, ballotBoxId, test);
        return Response.ok().entity(entity)
            .header("Content-Disposition", "attachment; filename=EncryptedBallotBox.csv;").build();
    }

    /**
     * Returns the result of validate if all ballot boxes for a given tenant,
     * election event and ballot box are empty.
     *
     * @param tenantId
     *            - the tenant identifier.
     * @param electionEventId
     *            - the election event identifier.
     * @param ballotBoxId
     *            - the ballot box identifier.
     * @return Returns the result of the validation.
     */
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("tenant/{tenantId}/electionevent/{electionEventId}/ballotbox/{ballotBoxId}/status")
    public Response checkIfBallotBoxIsEmpty(@HeaderParam(Constants.PARAMETER_HEADER_X_REQUEST_ID) 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) {

        trackIdInstance.setTrackId(trackingId);

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

        final com.scytl.products.ov.commons.validation.ValidationResult validationResult =
            ballotBoxService.checkIfBallotBoxesAreEmpty(tenantId, electionEventId, ballotBoxId);

        // convert to string
        final String json = gson.toJson(validationResult);

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

    /**
     * Returns the result of validate if all ballot boxes for a given tenant,
     * election event and ballot box is available.
     *
     * @param tenantId
     *            - the tenant identifier.
     * @param electionEventId
     *            - the election event identifier.
     * @param ballotBoxId
     *            - the ballot box identifier.
     * @return Returns the result of the validation.
     */
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("tenant/{tenantId}/electionevent/{electionEventId}/ballotbox/{ballotBoxId}/available")
    public Response checkIfBallotBoxIsAvailable(@HeaderParam(Constants.PARAMETER_HEADER_X_REQUEST_ID) 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) {

        trackIdInstance.setTrackId(trackingId);

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

        // The grace period wont'be considered for this validation
        ElectionValidationRequest electionValidationRequest =
            ElectionValidationRequest.create(tenantId, electionEventId, ballotBoxId, false);
        ValidationError validationResult = electionService.validateIfElectionIsOpen(electionValidationRequest);

        // convert to string
        final String json = gson.toJson(validationResult);

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

    @POST
    @Path("secured/tenant/{tenantId}/electionevent/{electionEventId}/block")
    public Response blockBallotBoxes(@HeaderParam(Constants.PARAMETER_HEADER_X_REQUEST_ID) String trackingId,
            @PathParam(QUERY_PARAMETER_TENANT_ID) final String tenantId,
            @PathParam(QUERY_PARAMETER_ELECTION_EVENT_ID) final String electionEventId,
            @NotNull final List<EntityId> ballotBoxIds, @Context final HttpServletRequest request)
            throws ApplicationException, ResourceNotFoundException, EntryPersistenceException {
        trackIdInstance.setTrackId(trackingId);

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

        ballotBoxService.blockBallotBoxes(tenantId, electionEventId, ballotBoxIds);
        return Response.ok().build();
    }

    @POST
    @Path("secured/tenant/{tenantId}/electionevent/{electionEventId}/unblock")
    public Response unblockBallotBoxes(@HeaderParam(Constants.PARAMETER_HEADER_X_REQUEST_ID) String trackingId,
            @PathParam(QUERY_PARAMETER_TENANT_ID) final String tenantId,
            @PathParam(QUERY_PARAMETER_ELECTION_EVENT_ID) final String electionEventId,
            @NotNull final List<EntityId> ballotBoxIds, @Context final HttpServletRequest request)
            throws ApplicationException, ResourceNotFoundException, EntryPersistenceException {

        trackIdInstance.setTrackId(trackingId);

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

        ballotBoxService.unblockBallotBoxes(tenantId, electionEventId, ballotBoxIds);
        return Response.ok().build();
    }

    /**
     * Check if ballot box is blocked
     *
     * @param tenantId
     *            the tenant identifier
     * @param electionEventId
     *            the election event identifier
     * @param ballotBoxId
     *            the ballot box identifier
     * @param request
     *            the Http Servlet Request
     * @throws ApplicationException
     *             if the input parameters are not valid
     * @throws ResourceNotFoundException
     *             if the ballot box is not found
     */
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("tenant/{tenantId}/electionevent/{electionEventId}/ballotbox/{ballotBoxId}/isblocked")
    public Response isBlockedBallotBox(@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, ResourceNotFoundException {

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

        validateParameters(tenantId, electionEventId, ballotBoxId);

        boolean isBlocked = ballotBoxService.isBlockedBallotBox(tenantId, electionEventId, ballotBoxId);

        JsonObjectBuilder jsonObjectBuilder = Json.createObjectBuilder();
        jsonObjectBuilder.add(JSON_PARAMETER_ISBLOCKED, isBlocked);
        String json = gson.toJson(jsonObjectBuilder.build().toString());

        return Response.ok().entity(json).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);
        }

    }
}
