/*
 * 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.commons.test;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.sql.DataSource;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.products.ov.channel.cc.choicecodes.generation.service.ChoiceCodesGenerationService;
import com.scytl.products.ov.channel.cc.choicecodes.verification.service.ChoiceCodesComputationService;
import com.scytl.products.ov.channel.cc.choicecodes.verification.service.ChoiceCodesComputationServiceImpl;
import com.scytl.products.ov.channel.cc.choicecodes.verification.service.ComputedVerificationCardExistsException;
import com.scytl.products.ov.channel.cc.choicecodes.verification.service.ComputedVerificationCardsRepository;
import com.scytl.products.ov.commons.beans.domain.model.confirmation.ConfirmationMessage;
import com.scytl.products.ov.commons.dto.BallotCastingKeyVerificationInput;
import com.scytl.products.ov.commons.dto.ChoiceCodeVerificationDTO;
import com.scytl.products.ov.commons.dto.ElGamalComputationsValuesCodec;
import com.scytl.products.ov.commons.dto.PartialCodesInput;
import com.scytl.products.ov.commons.messaging.MessageListener;
import com.scytl.products.ov.commons.messaging.MessagingService;
import com.scytl.products.ov.commons.ui.ws.rs.ObjectMappers;

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {TestConfiguration.class })
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
@TestPropertySource(properties = {
        "queues.cg-comp.res=" + VoteCastConfirmationTest.TEST_GENERATION_COMPUTATION_RESPONSE_QUEUE,
        "queues.cv-comp.res=" + VoteCastConfirmationTest.TEST_VERIFICATION_COMPUTATION_RESPONSE_QUEUE })
public class VoteCastConfirmationAttemptsTest {

    static final String TEST_GENERATION_COMPUTATION_RESPONSE_QUEUE = "cg-comp-res";

    static final String TEST_VERIFICATION_COMPUTATION_RESPONSE_QUEUE = "cv-comp-res";

    private final static String ELECTION_EVENT_ID = "electionEventId";

    private final static String VERIFICATION_CARD_SET_ID = "verificationCardSetId";

    private final static String VERIFICATION_CARD_ID = "verificationCardId";

    private final static String GENERATION_REQUEST_ID = "generationRequestId";
    
    @Autowired
    @Qualifier("TransactionAware")
    private DataSource dataSource;

    @Autowired
    MessagingService messagingService;

    @Autowired
    ChoiceCodesGenerationService choiceCodesGenerationService;

    @Autowired
    ChoiceCodesComputationService choiceCodesComputationService;

    @Autowired
    ElGamalComputationsValuesCodec codec;

    @Autowired
    ComputedVerificationCardsRepository computedVerificationCardsRepository;

    @BeforeClass
    public static void init() throws GeneralCryptoLibException {
        Security.addProvider(new BouncyCastleProvider());
    }

    @Test(expected = IllegalStateException.class)
    public void testUnsentVoteConfirmationFails() throws GeneralCryptoLibException, SQLException {
        
        // Prepare clear VCid states
        clearComputedVerificationCards();

        // Vote Cast Code Generation
        List<BigInteger> partialCodesElements = Arrays.asList(new BigInteger[]{ new BigInteger("9887") });
        
        ChoiceCodeVerificationDTO<PartialCodesInput> dtoVerification = new ChoiceCodeVerificationDTO<>();
        dtoVerification.setCorrelationId(UUID.randomUUID());
        dtoVerification.setRequestId(GENERATION_REQUEST_ID);
        dtoVerification.setElectionEventId(ELECTION_EVENT_ID);
        dtoVerification.setVerificationCardSetId(VERIFICATION_CARD_SET_ID);
        dtoVerification.setVerificationCardId(VERIFICATION_CARD_ID);

        BallotCastingKeyVerificationInput bckVerificationInput = new BallotCastingKeyVerificationInput();
        ConfirmationMessage confirmationMessage = new ConfirmationMessage();
        String confirmationKey =
                Base64.getEncoder().encodeToString(partialCodesElements.get(0).toString().getBytes(StandardCharsets.UTF_8));
        confirmationMessage.setConfirmationKey(confirmationKey);
        bckVerificationInput.setVotingCardId(VERIFICATION_CARD_ID);

        dtoVerification.setPayload(new PartialCodesInput());
        dtoVerification.getPayload().setBallotCastingKeyVerificationInput(bckVerificationInput);
        dtoVerification.getPayload().setPartialCodesElements(partialCodesElements);

        ((MessageListener) choiceCodesComputationService).onMessage(dtoVerification);
    }

    private void clearComputedVerificationCards() throws SQLException {
        String sql = "delete from computed_verification_cards";

        try (Connection connection = dataSource.getConnection();
                PreparedStatement statement = connection.prepareStatement(sql)) {
            statement.executeUpdate();
        } 
    }

    @Test
    public void testVoteConfirmationAttempsExceededError()
            throws GeneralCryptoLibException, ComputedVerificationCardExistsException, JsonProcessingException {

        // Prepare computed state of VCid
        computedVerificationCardsRepository.saveComputedVerificationCard(ELECTION_EVENT_ID, VERIFICATION_CARD_ID);

        // Vote Cast Code Generation
        List<BigInteger> partialCodesElements = Arrays.asList(new BigInteger[]{ new BigInteger("9887") });

        ChoiceCodeVerificationDTO<PartialCodesInput> dtoVerification = new ChoiceCodeVerificationDTO<>();
        dtoVerification.setCorrelationId(UUID.randomUUID());
        dtoVerification.setRequestId(GENERATION_REQUEST_ID);
        dtoVerification.setElectionEventId(ELECTION_EVENT_ID);
        dtoVerification.setVerificationCardSetId(VERIFICATION_CARD_SET_ID);
        dtoVerification.setVerificationCardId(VERIFICATION_CARD_ID);

        BallotCastingKeyVerificationInput bckVerificationInput = new BallotCastingKeyVerificationInput();
        ConfirmationMessage confirmationMessage = new ConfirmationMessage();
        String confirmationKey =
            Base64.getEncoder().encodeToString(partialCodesElements.get(0).toString().getBytes(StandardCharsets.UTF_8));
        confirmationMessage.setConfirmationKey(confirmationKey);
        bckVerificationInput.setConfirmationMessage(ObjectMappers.toJson(confirmationMessage));
        bckVerificationInput.setVotingCardId(VERIFICATION_CARD_ID);

        dtoVerification.setPayload(new PartialCodesInput());
        dtoVerification.getPayload().setBallotCastingKeyVerificationInput(bckVerificationInput);
        dtoVerification.getPayload().setPartialCodesElements(partialCodesElements);

        Map<String, Object> responseQueue = ((MessagingServiceMockImpl) messagingService).getQueueMock();

        for (int attempt = 1; attempt <= ChoiceCodesComputationServiceImpl.VOTE_CAST_MAX_ATTEMPTS; attempt++) {
            
            ((MessageListener) choiceCodesComputationService).onMessage(dtoVerification);            
            Assert.assertNotNull(responseQueue.remove(TEST_VERIFICATION_COMPUTATION_RESPONSE_QUEUE));
        }

        dtoVerification.getPayload().setPartialCodesElements(partialCodesElements);
        ((MessageListener) choiceCodesComputationService).onMessage(dtoVerification);

        // Test result: max attemps + 1 should not produce any more result
        Assert.assertNull(responseQueue.get(TEST_VERIFICATION_COMPUTATION_RESPONSE_QUEUE));
    }

}
