/*
 * 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.ei.services.domain.service.confirmation;

import static com.scytl.products.ov.ei.services.domain.model.util.Constants.ELECTION_EVENT_ID;
import static com.scytl.products.ov.ei.services.domain.model.util.Constants.TENANT_ID;
import static com.scytl.products.ov.ei.services.domain.model.util.Constants.VOTING_CARD_ID;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;

import com.scytl.products.ov.commons.test.CryptoUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.Security;
import java.security.cert.X509Certificate;

import javax.json.Json;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.runners.MockitoJUnitRunner;
import org.slf4j.Logger;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.certificates.bean.X509CertificateType;
import com.scytl.cryptolib.certificates.bean.X509DistinguishedName;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.products.ov.commons.beans.domain.model.authentication.AuthenticationToken;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.beans.validation.CertificateValidationService;
import com.scytl.products.ov.commons.beans.validation.ValidationErrorType;
import com.scytl.products.ov.commons.crypto.configuration.CertificateParameters;
import com.scytl.products.ov.commons.validation.ValidationError;
import com.scytl.products.ov.ei.services.domain.model.confirmation.ConfirmationInformation;
import com.scytl.products.ov.ei.services.domain.model.content.ElectionInformationContent;
import com.scytl.products.ov.ei.services.domain.model.content.ElectionInformationContentRepository;

@RunWith(MockitoJUnitRunner.class)
public class ConfirmationMessageCertificateChainValidationTest {

    public static final String COMMON_NAME = "commonName";

    private static final String ELECTION_ROOT_CA = "electionRootCA";

    private static final String CREDENTIALS_CA = "credentialsCA";
    
    @Mock
	private Logger LOG;
	    
    @InjectMocks
    private ConfirmationMessageCertificateChainValidation validation =
        new ConfirmationMessageCertificateChainValidation();

    @Mock
    private ElectionInformationContentRepository electionInformationContentRepository;

    @Mock
    private ConfirmationInformation confirmationInformation;

    @Mock
    private AuthenticationToken authenticationToken;

    @Mock
    private Logger logger;

    @Mock
    private ElectionInformationContent electionInformationContent;

    @Mock
    private CertificateValidationService certificateValidationService;

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this.getClass());
        
        doNothing().when(LOG).error(anyString());
    }

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

    @Rule
    public ExpectedException expectedException = ExpectedException.none();

    @Test
    public void testValidationOK() throws ResourceNotFoundException, IOException, GeneralCryptoLibException {

        when(electionInformationContentRepository.findByTenantIdElectionEventId(anyString(), anyString()))
            .thenReturn(electionInformationContent);

        final CryptoAPIX509Certificate certificate = getCertificate();
        String certificateAsString = new String(certificate.getPemEncoded(), StandardCharsets.UTF_8);

        final String json = getJson(certificateAsString);
        when(electionInformationContent.getJson()).thenReturn(json);
        when(confirmationInformation.getCertificate()).thenReturn(certificateAsString);

        doReturn(true).when(certificateValidationService).validateCertificateChain(any(X509Certificate.class),
            any(X509DistinguishedName.class), eq(X509CertificateType.CERTIFICATE_AUTHORITY),
            any(X509Certificate[].class), any(X509DistinguishedName[].class), any(X509Certificate.class));

        doReturn(true).when(certificateValidationService).validateCertificateChain(any(X509Certificate.class),
            any(X509DistinguishedName.class), eq(X509CertificateType.SIGN), any(X509Certificate[].class),
            any(X509DistinguishedName[].class), any(X509Certificate.class));

        final ValidationError execute = validation.execute(TENANT_ID, ELECTION_EVENT_ID, VOTING_CARD_ID,
            confirmationInformation, authenticationToken);
        assertEquals(execute.getValidationErrorType(), ValidationErrorType.SUCCESS);

    }

    @Test
    public void testValidationFailsInCA() throws ResourceNotFoundException, IOException, GeneralCryptoLibException {

        when(electionInformationContentRepository.findByTenantIdElectionEventId(anyString(), anyString()))
            .thenReturn(electionInformationContent);

        final CryptoAPIX509Certificate certificate = getCertificate();
        String certificateAsString = new String(certificate.getPemEncoded(), StandardCharsets.UTF_8);

        final String json = getJson(certificateAsString);
        when(electionInformationContent.getJson()).thenReturn(json);
        when(confirmationInformation.getCertificate()).thenReturn(certificateAsString);

        doReturn(false).when(certificateValidationService).validateCertificateChain(any(X509Certificate.class),
            any(X509DistinguishedName.class), eq(X509CertificateType.CERTIFICATE_AUTHORITY),
            any(X509Certificate[].class), any(X509DistinguishedName[].class), any(X509Certificate.class));

        final ValidationError execute = validation.execute(TENANT_ID, ELECTION_EVENT_ID, VOTING_CARD_ID,
            confirmationInformation, authenticationToken);
        assertEquals(execute.getValidationErrorType(), ValidationErrorType.FAILED);

    }

    @Test
    public void testValidationFailsInChainValidation()
            throws ResourceNotFoundException, IOException, GeneralCryptoLibException {

        when(electionInformationContentRepository.findByTenantIdElectionEventId(anyString(), anyString()))
            .thenReturn(electionInformationContent);

        final CryptoAPIX509Certificate certificate = getCertificate();
        String certificateAsString = new String(certificate.getPemEncoded(), StandardCharsets.UTF_8);

        final String json = getJson(certificateAsString);
        when(electionInformationContent.getJson()).thenReturn(json);
        when(confirmationInformation.getCertificate()).thenReturn(certificateAsString);

        doReturn(true).when(certificateValidationService).validateCertificateChain(any(X509Certificate.class),
            any(X509DistinguishedName.class), eq(X509CertificateType.CERTIFICATE_AUTHORITY),
            any(X509Certificate[].class), any(X509DistinguishedName[].class), any(X509Certificate.class));

        doReturn(false).when(certificateValidationService).validateCertificateChain(any(X509Certificate.class),
            any(X509DistinguishedName.class), eq(X509CertificateType.SIGN), any(X509Certificate[].class),
            any(X509DistinguishedName[].class), any(X509Certificate.class));

        final ValidationError execute = validation.execute(TENANT_ID, ELECTION_EVENT_ID, VOTING_CARD_ID,
            confirmationInformation, authenticationToken);
        assertEquals(execute.getValidationErrorType(), ValidationErrorType.FAILED);

    }

    @Test
    public void testValidationThrowsCryptoLibException()
            throws ResourceNotFoundException, IOException, GeneralCryptoLibException {

        when(electionInformationContentRepository.findByTenantIdElectionEventId(anyString(), anyString()))
            .thenReturn(electionInformationContent);

        final CryptoAPIX509Certificate certificate = getCertificate();
        String certificateAsString = new String(certificate.getPemEncoded(), StandardCharsets.UTF_8);

        final String json = getJson(certificateAsString);
        when(electionInformationContent.getJson()).thenReturn(json);
        when(confirmationInformation.getCertificate()).thenReturn(certificateAsString);

        doThrow(GeneralCryptoLibException.class).when(certificateValidationService).validateCertificateChain(
            any(X509Certificate.class), any(X509DistinguishedName.class), eq(X509CertificateType.CERTIFICATE_AUTHORITY),
            any(X509Certificate[].class), any(X509DistinguishedName[].class), any(X509Certificate.class));

        final ValidationError execute = validation.execute(TENANT_ID, ELECTION_EVENT_ID, VOTING_CARD_ID,
            confirmationInformation, authenticationToken);
        assertEquals(execute.getValidationErrorType(), ValidationErrorType.FAILED);

    }

    @Test
    public void testValidationThrowsResourceNotFoundException()
            throws ResourceNotFoundException, IOException, GeneralCryptoLibException {

        when(electionInformationContentRepository.findByTenantIdElectionEventId(anyString(), anyString()))
            .thenThrow(new ResourceNotFoundException("exception"));

        final ValidationError execute = validation.execute(TENANT_ID, ELECTION_EVENT_ID, VOTING_CARD_ID,
            confirmationInformation, authenticationToken);
        assertEquals(execute.getValidationErrorType(), ValidationErrorType.FAILED);

    }

    private CryptoAPIX509Certificate getCertificate() throws IOException, GeneralCryptoLibException {
        final KeyPair keyPairForSigning = CryptoUtils.getKeyPairForSigning();
        return CryptoUtils.createCryptoAPIx509Certificate(COMMON_NAME, CertificateParameters.Type.SIGN,
            keyPairForSigning);

    }

    private String getJson(String certificate) {

        return Json.createObjectBuilder().add(CREDENTIALS_CA, certificate).add(ELECTION_ROOT_CA, certificate).build()
            .toString();

    }

}
