/*
 * 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.application.service;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.ObjectNode;
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.stereotype.Service;

import com.scytl.products.ov.commons.beans.Ballot;
import com.scytl.products.ov.commons.beans.exceptions.ApplicationException;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.beans.validation.CryptographicOperationException;
import com.scytl.products.ov.commons.ui.ws.rs.ObjectMappers;
import com.scytl.products.ov.commons.util.JsonUtils;
import com.scytl.products.ov.sdm.domain.model.ballot.BallotRepository;
import com.scytl.products.ov.sdm.domain.model.ballotbox.BallotBoxRepository;
import com.scytl.products.ov.sdm.domain.model.ballotboxcounting.BallotBoxCountingRepository;
import com.scytl.products.ov.sdm.domain.model.counting.CountingInput;
import com.scytl.products.ov.sdm.domain.model.status.Status;
import com.scytl.products.ov.sdm.infrastructure.JsonConstants;
import com.scytl.products.ov.sdm.infrastructure.service.ConfigurationEntityStatusService;

/**
 * Service which will invoke the counting operation
 */
@Service
public class CountingService {

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

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

    @Autowired
    private BallotBoxRepository ballotBoxRepository;

    @Autowired
    private BallotRepository ballotRepository;

    @Autowired
    BallotBoxCountingRepository ballotBoxCountingRepository;

    @Autowired
    private ConfigurationEntityStatusService configurationEntityStatusService;

    @Value("${COUNTING_URL}")
    private String countingURL;

    @Value("${counting.enabled:false}")
    private boolean enabled;

    /**
     * Invokes the ballot box counting end point , with all the necessary
     * information
     * 
     * @param electionEventId
     *            - identifier of the election Event id
     * @param ballotBoxId
     *            - identifier of the election Event Id
     * @return
     * @throws ResourceNotFoundException
     * @throws CryptographicOperationException
     * @throws IOException
     * @throws ApplicationException
     */
    public Boolean countBallotBox(String electionEventId, String ballotBoxId)
            throws ResourceNotFoundException, IOException, ApplicationException {
        if (!enabled) {
            throw new IllegalStateException("Counting is not enabled.");
        }

        // find the ballot box
        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 ballotBoxJson = ballotBoxRepository.list(attributeValueMap);
        JsonArray ballotBoxJsonArray =
            JsonUtils.getJsonObject(ballotBoxJson).getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_RESULT);

        // Ok?
        if (!ballotBoxJsonArray.isEmpty()) {

            JsonObject ballotBoxJsonObject = ballotBoxJsonArray.getJsonObject(0);
            // check if state is "mixed"
            if (!ballotBoxJsonObject.getString(JsonConstants.JSON_ATTRIBUTE_NAME_STATUS)
                .equals(Status.DECRYPTED.name())) {
                throw new ApplicationException("Ballot box is not decrypted");
            }

            String ballotId = ballotBoxJsonObject.getJsonObject(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOT)
                .getString(JsonConstants.JSON_ATTRIBUTE_NAME_ID);

            String ballotAsJson = ballotRepository.find(ballotId);
            final String[] fields = {"defaultTitle", "defaultDescription", "alias", "defaultText", "status",
                    "ballotBoxes", "details", "synchronized" };
            ballotAsJson = JsonUtils.removeFieldsFromJson(ballotAsJson, fields);

            // remove election event from contests array in a ballot.
            JsonFactory factory = new JsonFactory();
            JsonParser parser = factory.createJsonParser(ballotAsJson);
            ObjectMapper mapper = new ObjectMapper(factory);
            JsonNode rootNode = mapper.readTree(parser);
            ArrayNode jsonArrayNode = (ArrayNode) rootNode.get("contests");
            if (jsonArrayNode != null) {
                for (JsonNode jsonNodeFromArray : jsonArrayNode) {
                    ObjectNode object = (ObjectNode) jsonNodeFromArray;
                    object.remove(JsonConstants.JSON_ATTRIBUTE_NAME_ELECTION_EVENT);
                }
            }
            ballotAsJson = rootNode.toString();

            Ballot ballot = ObjectMappers.fromJson(ballotAsJson, Ballot.class);
            CountingInput input = new CountingInput();
            input.setBallot(ballot);
            input.setBallotBoxID(ballotBoxId);
            input.setTenantID(tenantId);

            // Run counting process in the crypto operations server
            Response response;
            try {

                WebTarget ballotBoxClient = ClientBuilder.newClient().target(countingURL);

                response = ballotBoxClient.request()
                    .post(Entity.entity(ObjectMappers.toJson(input), MediaType.APPLICATION_JSON));
            } catch (ProcessingException e) {
                LOGGER.error("Error performing post request", e);
                return false;
            }

            if (response.getStatus() == Response.Status.OK.getStatusCode()) {
                // the ballot id used for save ballot box in the ballot
                // directory

                String countResult = response.readEntity(String.class);

                JsonObjectBuilder builder = JsonUtils.jsonObjectToBuilder(JsonUtils.getJsonObject(countResult));
                builder.add(JsonConstants.JSON_ATTRIBUTE_NAME_ELECTION_EVENT,
                    Json.createObjectBuilder().add(JsonConstants.JSON_ATTRIBUTE_NAME_ID, electionEventId));
                builder.add(JsonConstants.JSON_ATTRIBUTE_NAME_ID, ballotBoxId);

                ballotBoxCountingRepository.save(builder.build().toString());

                // change status to "COUNTED"
                configurationEntityStatusService.update(Status.COUNTED.name(),
                    ballotBoxJsonObject.getString(JsonConstants.JSON_ATTRIBUTE_NAME_ID), ballotBoxRepository);

                LOGGER.info("Successfully COUNTED ballot box");
                return true;
            }

        }

        return false;
    }

    /**
     * Returns whether the service is enabled.
     * 
     * @return the service is enabled.
     */
    public boolean isEnabled() {
        return enabled;
    }
}
