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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

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.exceptions.ApplicationException;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.sdm.application.log.SdmSecureLogEvent;
import com.scytl.products.ov.sdm.application.service.IdleStatusService;
import com.scytl.products.ov.sdm.application.service.MixDecValService;
import com.scytl.products.ov.sdm.domain.common.IdleState;
import com.scytl.products.ov.sdm.domain.model.mixing.MixingData;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;

/**
 * The mixing end-point.
 */
@RestController
@RequestMapping("/sdm-ws-rest/mixing")
@Api(value = "Mixing REST API")
public class MixDecValResource {

    private static final Logger LOGGER = LoggerFactory.getLogger(MixDecValResource.class);

    @Autowired
    private MixDecValService mixingService;

    @Autowired
    private IdleStatusService idleStatusService;

    @Autowired
    private TransactionInfoProvider transactionInfoProvider;

    @Autowired
    private SecureLoggingWriter secureLogger;

    @Value("${tenantID}")
    private String tenantId;

    /**
     * Performs the mixing of certain ballot box
     *
     * @param electionEventId
     *            the election event id.
     * @param ballotBoxId
     *            the ballot box id.
     * @return Status 200 on success
     */
    @RequestMapping(value = "/electionevent/{electionEventId}/ballotbox/{ballotBoxId}", method = RequestMethod.POST, produces = "application/json")
    @ResponseBody
    @ApiOperation(value = "Mix ballot box", notes = "Service to mix a given ballot box.", response = String.class)
    @ApiResponses(value = {@ApiResponse(code = 404, message = "Not Found"),
            @ApiResponse(code = 403, message = "Forbidden"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    public ResponseEntity<IdleState> getBallotBox(
            @ApiParam(value = "String", required = true) @PathVariable String electionEventId,
            @ApiParam(value = "String", required = true) @PathVariable String ballotBoxId,
            @ApiParam(value = "MixingData", required = true) @RequestBody final MixingData mixingData) {
        // If the ballot box is already being operated on, exit immediately.
        IdleState idleState = new IdleState();
        if (!(idleStatusService.isIdReady(ballotBoxId))) {
            idleState.setIdle(true);
            return new ResponseEntity<>(idleState, HttpStatus.OK);
        }

        transactionInfoProvider.generate(tenantId, "", "");

        secureLogger.log(Level.INFO, new LogContent.LogContentBuilder().logEvent(SdmSecureLogEvent.MIXING_BALLOT_BOX)
            .objectId(ballotBoxId).electionEvent(electionEventId).createLogInfo());

        ResponseEntity<IdleState> responseEntity;

        try {
            if (mixingService.mixDecryptValidate(electionEventId, ballotBoxId, mixingData.getPrivateKeyPEM(),
                mixingData.getElectoralAuthorityPrivateKey())) {

                responseEntity = new ResponseEntity<>(HttpStatus.OK);

                idleStatusService.freeIdleStatus(ballotBoxId);

                secureLogger.log(Level.INFO,
                    new LogContent.LogContentBuilder().logEvent(SdmSecureLogEvent.BALLOT_BOX_MIXED_SUCCESSFULLY)
                        .objectId(ballotBoxId).electionEvent(electionEventId).createLogInfo());
            } else {
                responseEntity = new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
                idleStatusService.freeIdleStatus(ballotBoxId);
            }
        } catch (ResourceNotFoundException e) {
        	LOGGER.warn("A resource was missing when performing the mixing", e);
            reportFailure("A resource was missing when performing the mixing", e, ballotBoxId, electionEventId);
            responseEntity = new ResponseEntity<>(HttpStatus.NOT_FOUND);
        } catch (ApplicationException e) {
        	LOGGER.warn("The request can not be performed for the current resource status", e);
            reportFailure("The request can not be performed for the current resource status", e, ballotBoxId,
                electionEventId);
            responseEntity = new ResponseEntity<>(HttpStatus.FORBIDDEN);
        } catch (Exception e) {
        	LOGGER.error("Internal error when performing the mixing", e);
            // Remove the ballot box ID from the idle status.
            idleStatusService.freeIdleStatus(ballotBoxId);
            responseEntity = new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }

        return responseEntity;
    }

    /**
     * Takes the necessary steps to report a failure in the mix+dec+val process.
     *
     * @param electionEventId
     *            the current election event ID
     * @param ballotBoxId
     *            the ballot box that was being processed
     * @param message
     *            a message indicating the failure
     * @param e
     *            the exception that resulted in the failure
     */
    private void reportFailure(String message, Throwable e, String ballotBoxId, String electionEventId) {

        idleStatusService.freeIdleStatus(ballotBoxId);

        LOGGER.error(message, e);

        secureLogger.log(Level.ERROR,
            new LogContent.LogContentBuilder().logEvent(SdmSecureLogEvent.BALLOT_BOX_MIXING_FAILED)
                .objectId(ballotBoxId).electionEvent(electionEventId)
                .additionalInfo("err_desc", message + ": " + e.getMessage()).createLogInfo());
    }
}
