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

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyPair;
import java.security.Security;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.cryptolib.certificates.bean.CertificateData;
import com.scytl.cryptolib.certificates.bean.ValidityDates;
import com.scytl.cryptolib.certificates.bean.X509DistinguishedName;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.cryptolib.certificates.service.CertificatesService;
import com.scytl.cryptolib.elgamal.bean.ElGamalEncryptionParameters;
import com.scytl.cryptolib.elgamal.bean.ElGamalPrivateKey;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.products.ov.channel.cc.choicecodes.generation.service.ChoiceCodesKeyGenerationServiceImpl;
import com.scytl.products.ov.channel.cc.commons.keymanagement.KeyManager;
import com.scytl.products.ov.channel.cc.commons.keymanagement.KeyNotFoundException;
import com.scytl.products.ov.channel.cc.commons.services.ChoiceCodesKeyRepositoryImpl;
import com.scytl.products.ov.commons.dto.KeyCreationDTO;
import com.scytl.products.ov.commons.dto.KeyType;
import com.scytl.products.ov.commons.messaging.DestinationNotFoundException;
import com.scytl.products.ov.commons.messaging.InvalidMessageException;
import com.scytl.products.ov.commons.messaging.MessagingException;
import com.scytl.products.ov.commons.messaging.MessagingService;
import com.scytl.products.ov.commons.messaging.Queue;

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = TestConfiguration.class)
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class ChoiceCodeKeyGenerationQueueTest {

    @Autowired
    private MessagingService messagingService;

    @Autowired
    private ChoiceCodesKeyGenerationServiceImpl service;

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

    @Test
    public void testKeyCreationHappyPath()
            throws GeneralCryptoLibException, DestinationNotFoundException,
            InvalidMessageException, MessagingException {
        KeyCreationDTO data = new KeyCreationDTO();
        data.setElectionEventId(UUID.randomUUID().toString());
        ElGamalEncryptionParameters encryptionParameters =
            new ElGamalEncryptionParameters(new BigInteger("809"),
                new BigInteger("68"), new BigInteger("3"));
        data.setEncryptionParameters(encryptionParameters.toJson());
        data.setRequestId(UUID.randomUUID().toString());
        data.setResourceId(UUID.randomUUID().toString());

        ArgumentCaptor<KeyCreationDTO> returnedDTOs =
            ArgumentCaptor.forClass(KeyCreationDTO.class);

        service.onMessage(data);
        verify(messagingService, atLeastOnce()).send(any(Queue.class), returnedDTOs.capture());

        List<KeyCreationDTO> allValues = returnedDTOs.getAllValues();
        assertEquals(1, allValues.size());
        KeyCreationDTO returnedDTO = allValues.get(0);
        assertEquals(returnedDTO.getElectionEventId(),
            data.getElectionEventId());
        assertEquals(returnedDTO.getEncryptionParameters(),
            data.getEncryptionParameters());
        assertEquals(returnedDTO.getRequestId(), data.getRequestId());
        assertEquals(returnedDTO.getResourceId(), data.getResourceId());
        assertEquals(2, returnedDTO.getPublicKeys().size());
        assertEquals(KeyType.CHOICE_CODE_GENERATION,
            returnedDTO.getPublicKeys().get(0).getKeytype());
        assertEquals(ElGamalPublicKey.fromJson(TestConfiguration.PUBLIC_KEY),
            returnedDTO.getPublicKeys().get(0).getPublicKey());
        assertArrayEquals("Signature".getBytes(StandardCharsets.UTF_8),
            returnedDTO.getPublicKeys().get(0).getKeySignature());
        assertNotNull(
            returnedDTO.getPublicKeys().get(0).getSignerCertificate());
        assertEquals(KeyType.CHOICE_CODE_DECRYPTION,
            returnedDTO.getPublicKeys().get(1).getKeytype());
        assertEquals(ElGamalPublicKey.fromJson(TestConfiguration.PUBLIC_KEY),
            returnedDTO.getPublicKeys().get(1).getPublicKey());
        assertArrayEquals("Signature".getBytes(StandardCharsets.UTF_8),
            returnedDTO.getPublicKeys().get(1).getKeySignature());
        assertNotNull(
            returnedDTO.getPublicKeys().get(1).getSignerCertificate());
    }

    @Configuration
    @Import({ChoiceCodesKeyGenerationServiceImpl.class, ChoiceCodesKeyRepositoryImpl.class})
    public static class Config {
        public static final String PUBLIC_KEY = "{\"publicKey\":{\"zpSubgroup\":{\"g\":\"Ag==\",\"p\":\"AIGt8wzizyJ8+ia716YgY4FjG5gJRjfz+iB3OeWEMLCOLnoWG0wHwFQyo2VNx4lgBPDmSIb5NtJ/12U3dfe7JdgYjEMSaz9HUrfowHNwCuD1VVmDXxUvyxPSV5/iss7a85sopvR9n09imvvp8XDfPNuccd10TQwGSKVt2vN7vSMrgQOsGEuBBsEmsdlRil4+x9NTYphsc/7vTV9pRN4U+ZI5yTbBof6SFcH5mwAatlZB/RztwA1wh3qUUgyLz/kTKqTRbaPc5W1+LDPBZnMiaTWq786418pio4oM86mVIthfE/34S0kn8GpJGzNF12NzbVQzbpv6YpmrPKG1FfaQPwc=\",\"q\":\"QNb5hnFnkT59E13r0xAxwLGNzASjG/n9EDuc8sIYWEcXPQsNpgPgKhlRsqbjxLACeHMkQ3ybaT/rspu6+92S7AxGIYk1n6OpW/RgObgFcHqqrMGvipfliekrz/FZZ215zZRTej7Pp7FNffT4uG+ebc447romhgMkUrbteb3ekZXAgdYMJcCDYJNY7KjFLx9j6amxTDY5/3emr7Sibwp8yRzkm2DQ/0kK4PzNgA1bKyD+jnbgBrhDvUopBkXn/ImVUmi20e5ytr8WGeCzOZE0mtV351xr5TFRxQZ51MqRbC+J/vwlpJP4NSSNmaLrsbm2qhm3Tf0xTNWeUNqK+0gfgw==\"},\"elements\":[\"cDEirCGORSF6n3eWy0zxrjASTNBes9xCXLEJL9AigcUtAEauqgzQEOE+r2SE1eIXHbf43Iv4RT1ffKD6msPbKOppmB6bdxOtlzHK/6HJxJ9z6zNa75OTB2NbRCxGLhuN92joFFN8uQqiZ87PBYE4ZmA347gjVSvJsolsoFki2r4gLRITFbo/0CYFZqUXUgIzbyTX7DYIjCWY9o4sXofd1Ay3QgdHvN6HEVpdUc62bMxu1q9wZopPFdA03fadGyKeQkT93EgIAcCSuMemfgNEcONEieMSg3pLIKy68dKfxIP7y09WdY4f+to6d+xJVU/61/Gp41yPNS9n9BruQgsdAA==\"]}}";

        public static final String PRIVATE_KEY = "{\"privateKey\":{\"zpSubgroup\":{\"g\":\"Ag==\",\"p\":\"AIGt8wzizyJ8+ia716YgY4FjG5gJRjfz+iB3OeWEMLCOLnoWG0wHwFQyo2VNx4lgBPDmSIb5NtJ/12U3dfe7JdgYjEMSaz9HUrfowHNwCuD1VVmDXxUvyxPSV5/iss7a85sopvR9n09imvvp8XDfPNuccd10TQwGSKVt2vN7vSMrgQOsGEuBBsEmsdlRil4+x9NTYphsc/7vTV9pRN4U+ZI5yTbBof6SFcH5mwAatlZB/RztwA1wh3qUUgyLz/kTKqTRbaPc5W1+LDPBZnMiaTWq786418pio4oM86mVIthfE/34S0kn8GpJGzNF12NzbVQzbpv6YpmrPKG1FfaQPwc=\",\"q\":\"QNb5hnFnkT59E13r0xAxwLGNzASjG/n9EDuc8sIYWEcXPQsNpgPgKhlRsqbjxLACeHMkQ3ybaT/rspu6+92S7AxGIYk1n6OpW/RgObgFcHqqrMGvipfliekrz/FZZ215zZRTej7Pp7FNffT4uG+ebc447romhgMkUrbteb3ekZXAgdYMJcCDYJNY7KjFLx9j6amxTDY5/3emr7Sibwp8yRzkm2DQ/0kK4PzNgA1bKyD+jnbgBrhDvUopBkXn/ImVUmi20e5ytr8WGeCzOZE0mtV351xr5TFRxQZ51MqRbC+J/vwlpJP4NSSNmaLrsbm2qhm3Tf0xTNWeUNqK+0gfgw==\"},\"exponents\":[\"DWOyiVH88d/gzjBCQZenbbstssyQKlHOo5+dpbZ3OV2Wko8K39cqCTHZmn1xXmFWSm/GIN6oI0IzMFHRdECGuFgEzF6a+SbYijtJyp2IodHvLvbul7ZSEj5Z1m4giGrQ+NWppu3kHZzP6u1gu7sY4BF3sAwT9rGPZrc9HadMdXspip5dxUVv30y0jTUenbksfUh/CoIpCxQXSMhIqYYZv/bK9RAKVwo1L3QeiucpefqF/14/4Li/idsskvkrsfOHixNJ2j3UV5zvPNrfyRA3oLxi1Ci9DDeQ3S12Svg9mzrKuuU0TXGwx509YubAi3fz6uUyi24tQCVmjQZ5ZGGeNA==\"]}}";
        
        @Bean
        public MessagingService messagingService() {
            return mock(MessagingService.class);
        }
        
        @Bean
        public KeyManager keyManager() throws KeyNotFoundException, KeyManagementException, GeneralCryptoLibException {
        	KeyManager manager = mock(KeyManager.class);
        	when(manager.getChoiceCodeDecryptionPrivateKey(any(), any())).thenReturn(ElGamalPrivateKey.fromJson(PRIVATE_KEY));
        	when(manager.getChoiceCodeDecryptionPublicKey(any(), any())).thenReturn(ElGamalPublicKey.fromJson(PUBLIC_KEY));
        	when(manager.getChoiceCodeGenerationPrivateKey(any(), any())).thenReturn(ElGamalPrivateKey.fromJson(PRIVATE_KEY));
        	when(manager.getChoiceCodeGenerationPublicKey(any(), any())).thenReturn(ElGamalPublicKey.fromJson(PUBLIC_KEY));
        	when(manager.hasValidElectionSigningKeys(any(), any(Date.class), any(Date.class))).thenReturn(Boolean.TRUE);
        
        	when(manager.getChoiceCodeDecryptionPublicKeySignature(any(), any())).thenReturn("Signature".getBytes(StandardCharsets.UTF_8));
        	when(manager.getChoiceCodeGenerationPublicKeySignature(any(), any())).thenReturn("Signature".getBytes(StandardCharsets.UTF_8));
        	when(manager.getElectionSigningCertificate(any())).thenReturn(createTestCertificate().getCertificate());
        	return manager;
        }
        
        private CryptoAPIX509Certificate createTestCertificate() throws GeneralCryptoLibException {
            KeyPair keyPair = new AsymmetricService().getKeyPairForSigning();
            
            ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
            ZonedDateTime end = now.plusYears(1);
            ValidityDates validityDates = new ValidityDates(Date.from(now.toInstant()), Date.from(end.toInstant()));
            
            CertificateData certificateData = new CertificateData();
            certificateData.setSubjectPublicKey(keyPair.getPublic());
            certificateData.setValidityDates(validityDates);
            X509DistinguishedName distinguishedName = new X509DistinguishedName.Builder("certId", "ES").build();
            certificateData.setSubjectDn(distinguishedName);
            certificateData.setIssuerDn(distinguishedName);
            
            return  new CertificatesService().createSignX509Certificate(certificateData, keyPair.getPrivate());
        }
    }
}
