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

import javax.json.JsonObject;

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.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
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.ResourceNotFoundException;
import com.scytl.products.ov.commons.infrastructure.remote.client.RetrofitException;
import com.scytl.products.ov.commons.util.JsonUtils;
import com.scytl.products.ov.sdm.application.log.SdmSecureLogEvent;
import com.scytl.products.ov.sdm.application.service.BallotBoxDownloadException;
import com.scytl.products.ov.sdm.application.service.BallotBoxDownloadService;
import com.scytl.products.ov.sdm.application.service.BallotBoxService;
import com.scytl.products.ov.sdm.application.service.CleansingOutputsDownloadException;
import com.scytl.products.ov.sdm.application.service.CleansingOutputsDownloadService;
import com.scytl.products.ov.sdm.application.service.IdleStatusService;
import com.scytl.products.ov.sdm.domain.common.IdleState;
import com.scytl.products.ov.sdm.domain.model.ballotbox.BallotBoxRepository;
import com.scytl.products.ov.sdm.domain.model.ballotbox.BallotBoxSignInputData;
import com.scytl.products.ov.sdm.infrastructure.JsonConstants;

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 ballot box end-point.
 */
@RestController
@RequestMapping("/sdm-ws-rest/ballotboxes")
@Api(value = "Ballot Box REST API")
public class BallotBoxResource {

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

    // The ballot box repository
    @Autowired
    private BallotBoxRepository ballotBoxRepository;

    // The ballot box download service
    @Autowired
    private BallotBoxDownloadService ballotBoxDownloadService;

    // The ballot box processing service
    @Autowired
    private BallotBoxService ballotBoxService;

    // The configuration of contests / elections
    @Value("${elections.config.filename}")
    private String configFile;

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

    @Autowired
    IdleStatusService idleStatusService;

    @Autowired
    private TransactionInfoProvider transactionInfoProvider;

    @Autowired
    private SecureLoggingWriter secureLogger;

    @Autowired
    private CleansingOutputsDownloadService cleansingOutputsDownloadService;

    /**
     * Check if ballot boxes are ready to be downloaded (mixed)
     *
     * @param electionEventId
     *            the election event id.
     * @throws IOException
     * @throws RetrofitException
     */
    @RequestMapping(value = "/electionevent/{electionEventId}/status", method = RequestMethod.POST, produces = "application/json")
    @ResponseStatus(value = HttpStatus.OK)
    @ApiOperation(value = "Check if ballot boxes are ready to be downloaded (mixed)")
    public void updateBallotBoxesMixingStatus(
            @ApiParam(value = "String", required = true) @PathVariable String electionEventId)
            throws RetrofitException, IOException {

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

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

        ballotBoxService.updateBallotBoxesMixingStatus(electionEventId);
    }

    /**
     * Returns an ballot box identified by election event and its id.
     *
     * @param electionEventId
     *            the election event id.
     * @param ballotBoxId
     *            the ballot box id.
     * @return An ballot box identified by election event and its id.
     */
    @RequestMapping(value = "/electionevent/{electionEventId}/ballotbox/{ballotBoxId}", method = RequestMethod.GET, produces = "application/json")
    @ResponseBody
    @ApiOperation(value = "Get Ballot box", notes = "Service to retrieve a given ballot box.", response = String.class)
    public ResponseEntity<String> getBallotBox(
            @ApiParam(value = "String", required = true) @PathVariable String electionEventId,
            @ApiParam(value = "String", required = true) @PathVariable String ballotBoxId) {
        Map<String, Object> attributeValueMap = new HashMap<>();
        attributeValueMap.put(JsonConstants.JSON_ATTRIBUTE_NAME_ID, ballotBoxId);
        attributeValueMap.put(JsonConstants.JSON_STRUCTURE_NAME_ELECTION_EVENT_ID, electionEventId);
        String result = ballotBoxRepository.find(attributeValueMap);
        JsonObject jsonObject = JsonUtils.getJsonObject(result);
        if (!jsonObject.isEmpty()) {
            return new ResponseEntity<>(result, HttpStatus.OK);
        }
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }

    /**
     * Returns a list of all ballot boxes.
     *
     * @param electionEventId
     *            the election event id.
     * @return The list of ballot boxes.
     */
    @RequestMapping(value = "/electionevent/{electionEventId}", method = RequestMethod.GET, produces = "application/json")
    @ResponseBody
    @ApiOperation(value = "List of ballot boxes", notes = "Service to retrieve the list of ballot boxes.", response = String.class)
    public String getBallotBoxes(@ApiParam(value = "String", required = true) @PathVariable String electionEventId) {
        Map<String, Object> attributeValueMap = new HashMap<>();
        attributeValueMap.put(JsonConstants.JSON_STRUCTURE_NAME_ELECTION_EVENT_ID, electionEventId);

        return ballotBoxRepository.list(attributeValueMap);
    }

    /**
     * Download a ballot box identified by the corresponding election event and
     * its id.
     *
     * @param electionEventId
     *            the election event id.
     * @param ballotBoxId
     *            the ballot box id.
     * @return
     */
    @RequestMapping(value = "/electionevent/{electionEventId}/ballotbox/{ballotBoxId}", method = RequestMethod.POST)
    @ApiOperation(value = "Download ballot box", notes = "Service to download a given ballot box.")
    @ApiResponses(value = {@ApiResponse(code = 403, message = "Forbidden") })
    public ResponseEntity<IdleState> downloadBallotBox(
            @ApiParam(value = "String", required = true) @PathVariable String electionEventId,
            @ApiParam(value = "String", required = true) @PathVariable String ballotBoxId) {

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

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

        // checks firstly if the ballot box id is already doing an operation
        IdleState idleState = new IdleState();
        if (idleStatusService.isIdReady(ballotBoxId)) {
            try {
                // Download the mixing payloads.
                ballotBoxDownloadService.downloadPayloads(electionEventId, ballotBoxId);
                // Download the raw ballot box.
                ballotBoxDownloadService.download(electionEventId, ballotBoxId);
                // Download the successful and failed votes.
                cleansingOutputsDownloadService.download(electionEventId, ballotBoxId);
                // Mark the ballot box as downloaded.
                ballotBoxDownloadService.updateBallotBoxStatus(ballotBoxId);
                return new ResponseEntity<>(idleState, HttpStatus.OK);
            } catch (BallotBoxDownloadException e) {
                LOGGER.error("Error downloading ballot box ", e);
                return new ResponseEntity<>(idleState, HttpStatus.FORBIDDEN);
            } catch (CleansingOutputsDownloadException e) {
                LOGGER.error("Error downloading cleansing outputs ", e);
                return new ResponseEntity<>(idleState, HttpStatus.FORBIDDEN);
            } finally {
                idleStatusService.freeIdleStatus(ballotBoxId);
            }
        } else {
            idleState.setIdle(true);
            return new ResponseEntity<>(idleState, HttpStatus.OK);
        }
    }

    /**
     * Change the state of the ballot box from ready to signed for a given
     * election event and ballot box id.
     *
     * @param electionEventId
     *            the election event id.
     * @param ballotBoxId
     *            the ballot box id.
     * @return HTTP status code 200 - If the ballot box is successfully signed.
     *         HTTP status code 404 - If the resource is not found. HTTP status
     *         code 412 - If the ballot box is already signed.
     */
    @RequestMapping(value = "/electionevent/{electionEventId}/ballotbox/{ballotBoxId}", method = RequestMethod.PUT)
    @ApiOperation(value = "Sign ballot box", notes = "Service to change the state of the ballot box from ready to signed "
        + "for a given election event and ballot box id..", response = Void.class)
    @ApiResponses(value = {@ApiResponse(code = 404, message = "Not Found"),
            @ApiResponse(code = 412, message = "Precondition Failed") })
    public ResponseEntity<Void> signBallotBox(
            @ApiParam(value = "String", required = true) @PathVariable String electionEventId,
            @ApiParam(value = "String", required = true) @PathVariable String ballotBoxId,
            @ApiParam(value = "BallotBoxSignInputData", required = true) @RequestBody BallotBoxSignInputData input) {
        try {
            transactionInfoProvider.generate(tenantId, "", "");

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

            ballotBoxService.sign(electionEventId, ballotBoxId, input.getPrivateKeyPEM());
        } catch (ResourceNotFoundException e) {
            String errorMessage = "An error occurred while fetching the given ballot box to sign";
            LOGGER.error(errorMessage, e);
            secureLogger.log(Level.ERROR,
                new LogContent.LogContentBuilder().logEvent(SdmSecureLogEvent.BALLOT_BOX_SIGNING_FAILED)
                    .objectId(ballotBoxId).electionEvent(electionEventId)
                    .additionalInfo("err_desc", errorMessage + ": " + e.getMessage()).createLogInfo());

            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        } catch (GeneralCryptoLibException | IOException e) {
            String errorMessage = "An error occurred while signing the ballot box";
            LOGGER.error(errorMessage, e);
            secureLogger.log(Level.ERROR,
                new LogContent.LogContentBuilder().logEvent(SdmSecureLogEvent.BALLOT_BOX_SIGNING_FAILED)
                    .objectId(ballotBoxId).electionEvent(electionEventId)
                    .additionalInfo("err_desc", errorMessage + ": " + e.getMessage()).createLogInfo());
            return new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
        }

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

        return new ResponseEntity<>(HttpStatus.OK);
    }
}
