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

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.slf4j.Logger;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.products.ov.channel.cc.orchestrator.mixdec.infrastructure.persistence.MixDecBallotBoxRepository;
import com.scytl.products.ov.channel.cc.orchestrator.mixdec.infrastructure.persistence.MixDecNodeOutputRepository;
import com.scytl.products.ov.commons.beans.domain.model.messaging.PayloadSignature;
import com.scytl.products.ov.commons.beans.exceptions.ApplicationException;
import com.scytl.products.ov.commons.beans.exceptions.DuplicateEntryException;
import com.scytl.products.ov.commons.beans.exceptions.MixDecNodeOutputRepositoryException;
import com.scytl.products.ov.commons.dto.MixingDTO;
import com.scytl.products.ov.commons.dto.MixingDTOImpl;
import com.scytl.products.ov.commons.dto.mixing.MixingPayload;
import com.scytl.products.ov.commons.dto.mixing.MixingPayloadMockBuilder;

@RunWith(MockitoJUnitRunner.class)
public class MixDecMixingDecryptionConsumerTest {

    private static final Set<String> ALL_NODES = Stream.of("1", "2", "3").collect(Collectors.toSet());

    private static final String REQUEST_ID = "requestId";

    private static final List<String> votes = Arrays.asList("A", "B");

    @Mock
    private MixDecBallotBoxRepository ballotBoxRepository;

    @Mock
    private MixDecNodeOutputRepository nodeOutputRepository;

    @Mock
    private MixDecBallotBoxService service;

    @Mock
    private Logger LOG;

    @InjectMocks
    private MixDecMixingDecryptionConsumer sut = new MixDecMixingDecryptionConsumer();

    /**
     * Test a DTO's behaviour during its expected lifetime going through all
     * nodes.
     * 
     * @throws ApplicationException
     * @throws MixDecNodeOutputRepositoryException
     */
    @Test
    public void fullCycle()
            throws TimeoutException, DuplicateEntryException, GeneralCryptoLibException, ApplicationException,
            MixDecNodeOutputRepositoryException {
        MixingDTO dto = createTestDTO();
        MixingPayload payload = dto.getPayload();

        // The initial DTO does not have previous results in it.
        assertNull(payload.getShuffledVotes());
        assertNull(payload.getShuffleProof());
        assertNull(payload.getDecryptionProofs());

        // Prepare the data for the DTO as if it were coming out of a node.
        List<String> shuffledVotes = votes;
        Collections.reverse(shuffledVotes);

        // Process the DTO through all other nodes.
        do {
            // Ensure that previous calls don't interfere with the counts.
            reset(service);
            reset(nodeOutputRepository);

            // Pretend that some processing happens in a node.
            MixingPayload processedPayload =
                new MixingPayloadMockBuilder().withPayload(payload).withSignature(mock(PayloadSignature.class)).build();
            dto.update(dto.getQueuesToVisit().iterator().next(), ALL_NODES.iterator().next(), processedPayload);

            testDTO(dto);
        } while (!dto.getQueuesToVisit().isEmpty());

        // The ballot box and its status were updated.
        verify(service).addProcessedBallotBoxWithStatus(dto.getPayload());
    }

    @Test
    public void noProcessingOnErrorDTO()
            throws DuplicateEntryException, TimeoutException, GeneralCryptoLibException, ApplicationException {
        MixingDTO dto = createTestDTO();
        dto.setError(new RuntimeException("Failed"));

        verify(nodeOutputRepository, times(0)).save(any());
        verify(service, times(0)).process(any());
        verify(ballotBoxRepository, times(0)).save(anyString(), anyString(), anyString(), any());
    }

    private void testDTO(MixingDTO dto) throws DuplicateEntryException, TimeoutException, ApplicationException {
        MixingPayload payload = dto.getPayload();

        // A DTO coming out of a node has previous results in it.
        assertNotNull(payload.getShuffledVotes());
        assertNotNull(payload.getShuffleProof());
        assertNotNull(payload.getDecryptionProofs());

        // A DTO was received.
        sut.onMessage(dto);

        // The partial mixing results were saved.
        verify(nodeOutputRepository).save(any());

        // If there were other nodes to visit, the resulting DTO was resent.
        if (!dto.getQueuesToVisit().isEmpty()) {
            verify(service).process(any());
        }
    }

    private MixingDTO createTestDTO() throws GeneralCryptoLibException {
        // Create the payload that will be returned.
        return new MixingDTOImpl(REQUEST_ID, ALL_NODES,
            new MixingPayloadMockBuilder().withSignature(mock(PayloadSignature.class)).build());
    }
}
