/*
 * 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.choicecodes.domain.services;

import java.io.IOException;
import java.io.OutputStream;
import java.util.UUID;

import javax.inject.Inject;

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

import com.scytl.products.ov.channel.cc.orchestrator.choicecodes.domain.model.computedvalues.ChoiceCodesComputationStatus;
import com.scytl.products.ov.channel.cc.orchestrator.choicecodes.domain.model.computedvalues.ComputedValuesRepository;
import com.scytl.products.ov.channel.cc.orchestrator.commons.config.QueuesConfig;
import com.scytl.products.ov.commons.beans.exceptions.DuplicateEntryException;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.dto.ChoiceCodeGenerationDTO;
import com.scytl.products.ov.commons.dto.ChoiceCodeGenerationReqPayload;
import com.scytl.products.ov.commons.messaging.MessageListener;
import com.scytl.products.ov.commons.messaging.MessagingException;
import com.scytl.products.ov.commons.messaging.MessagingService;
import com.scytl.products.ov.commons.messaging.Queue;

public class ChoiceCodesGenerationContributionsService {

    @Inject
    private MessagingService messagingService;

    @Inject
    @ChoiceCodesGeneration
    private MessageListener contributionsConsumer;

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

    @Inject
    private ComputedValuesService choiceCodesService;

    @Inject
    private ComputedValuesRepository computedValuesRepository;

    public void request(String trackingId, ChoiceCodeGenerationReqPayload payload)
            throws DuplicateEntryException, ResourceNotFoundException {

        LOG.info("OR:{} - Requesting the choice codes compute contributions for generation phase", trackingId);

        String tenantId = payload.getTenantId();
        String electionEventId = payload.getElectionEventId();
        String verificationCardSetId = payload.getVerificationCardSetId();
        int chunkId = payload.getChunkId();

        boolean existsComputedValues =
            computedValuesRepository.existsByTenantIdElectionEventIdVerificationCardSetIdChunkId(tenantId,
                electionEventId, verificationCardSetId, chunkId);

        if (existsComputedValues) {
            throw new DuplicateEntryException(
                "Choice codes computations already exist for the tenant: " + tenantId + " election event "
                    + electionEventId + " verification card set id " + verificationCardSetId + " chunk id " + chunkId);
        }

        choiceCodesService.create(tenantId, electionEventId, verificationCardSetId, chunkId);

        ChoiceCodeGenerationDTO<ChoiceCodeGenerationReqPayload> dto =
            new ChoiceCodeGenerationDTO<>(UUID.randomUUID(), trackingId, payload);

        publishPartialCodesToReqQ(dto);
    }

    private void publishPartialCodesToReqQ(
            ChoiceCodeGenerationDTO<ChoiceCodeGenerationReqPayload> choiceCodeGenerationDTO)
            throws ResourceNotFoundException {

        Queue[] queues = QueuesConfig.GENERATION_CONTRIBUTIONS_REQ_QUEUES;
        if (queues.length == 0) {
            throw new IllegalStateException(
                "No choice codes compute contributions request queues provided to publish to.");
        }

        for (Queue queue : queues) {
            try {
                messagingService.send(queue, choiceCodeGenerationDTO);
            } catch (MessagingException e) {
                LOG.error("OR - Error publishing partial codes to the nodes queues " + e);
                throw new ResourceNotFoundException("Error publishing partial codes to the nodes queues ", e);
            }
        }
    }

    public void writeToStream(OutputStream stream, String tenantId, String electionEventId,
            String verificationCardSetId, int chunkId) throws IOException, ResourceNotFoundException {

        LOG.info("OR - Retrieving the choice codes compute contributions for generation phase");
        computedValuesRepository.writeJsonToStreamForTenantIdElectionEventIdVerificationCardSetIdChunkId(stream,
            tenantId, electionEventId, verificationCardSetId, chunkId);
    }

    /**
     * Check the status of choice code generation values.
     *
     * @throws ResourceNotFoundException
     */
    public ChoiceCodesComputationStatus getComputedValuesStatus(String electionEventId, String tenantId,
            String verificationCardSetId, int chunkId) throws ResourceNotFoundException {

        boolean exist = computedValuesRepository.existsByTenantIdElectionEventIdVerificationCardSetIdChunkId(tenantId,
            electionEventId, verificationCardSetId, chunkId);

        if (!exist) {
            throw new ResourceNotFoundException(
                "Did not find any record in the relevant table matching the specified parameters");
        }

        boolean computed = computedValuesRepository.isComputedByTenantIdElectionEventIdVerificationCardSetIdChunkId(
            tenantId, electionEventId, verificationCardSetId, chunkId);

        return computed ? ChoiceCodesComputationStatus.COMPUTED : ChoiceCodesComputationStatus.COMPUTING;
    }

    /**
     * Check the status of choice code generation values.
     *
     * @throws ResourceNotFoundException
     */
    public ChoiceCodesComputationStatus getCompositeComputedValuesStatus(String electionEventId, String tenantId,
            String verificationCardSetId, int chunkCount) throws ResourceNotFoundException {

        boolean exist = computedValuesRepository.existsByTenantIdElectionEventIdVerificationCardSetId(tenantId,
            electionEventId, verificationCardSetId);

        if (!exist) {
            throw new ResourceNotFoundException(
                "Did not find any record in the relevant table matching the specified parameters");
        }

        boolean computed = computedValuesRepository.areComputedByTenantIdElectionEventIdVerificationCardSetId(tenantId,
            electionEventId, verificationCardSetId, chunkCount);

        return computed ? ChoiceCodesComputationStatus.COMPUTED : ChoiceCodesComputationStatus.COMPUTING;
    }

    public void startup() throws MessagingException {

        LOG.info("OR - Consuming the choice codes compute contributions queues");

        Queue[] queues = QueuesConfig.GENERATION_CONTRIBUTIONS_RES_QUEUES;

        if (queues.length == 0) {
            throw new IllegalStateException(
                "No choice codes compute contributions response queues provided to listen to.");
        }

        for (Queue queue : queues) {
            messagingService.createReceiver(queue, contributionsConsumer);
        }
    }

    public void shutdown() throws MessagingException {

        LOG.info("OR - Disconnecting from the choice codes compute contributions queues");

        Queue[] queues = QueuesConfig.GENERATION_CONTRIBUTIONS_RES_QUEUES;

        if (queues.length == 0) {
            throw new IllegalStateException(
                "No choice codes compute contributions response queues provided to listen to.");
        }

        for (Queue queue : queues) {
            messagingService.destroyReceiver(queue, contributionsConsumer);
        }
    }

}
