/*
 * 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.au.services.domain.service.validation;

import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
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.PublicKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;

import javax.json.Json;
import javax.json.JsonObject;

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.runners.MockitoJUnitRunner;
import org.slf4j.Logger;

import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.products.ov.au.services.domain.model.authentication.AuthenticationCerts;
import com.scytl.products.ov.au.services.domain.model.authentication.AuthenticationCertsRepository;
import com.scytl.products.ov.au.services.domain.model.authentication.AuthenticationToken;
import com.scytl.products.ov.au.services.domain.model.information.VoterInformation;
import com.scytl.products.ov.au.services.domain.utils.BeanUtils;
import com.scytl.products.ov.commons.beans.domain.model.authentication.AuthenticationContent;
import com.scytl.products.ov.commons.beans.exceptions.AuthTokenValidationException;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.beans.validation.CryptographicOperationException;
import com.scytl.products.ov.commons.crypto.CertificateChainRepository;
import com.scytl.products.ov.commons.crypto.InputDataFormatterService;
import com.scytl.products.ov.commons.crypto.SignatureForObjectService;
import com.scytl.products.ov.commons.crypto.configuration.CertificateParameters;
import com.scytl.products.ov.commons.validation.ValidationResult;

@RunWith(MockitoJUnitRunner.class)
public class AuthenticationTokenSignatureValidationTest {

    private static final String KEYSTORE_ALIAS = "alias";

    public static final String COMMON_NAME = "commonName";

    @InjectMocks
    AuthenticationTokenSignatureValidation validation = new AuthenticationTokenSignatureValidation();

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

    @Mock
    private Logger logger;

    @Mock
    AuthenticationToken authenticationTokenMock;

    @Mock
    private AuthenticationContent authenticationContentMock;

    @Mock
    private AsymmetricServiceAPI asymmetricServiceMock;

    @Mock
    private SignatureForObjectService signatureServiceMock;

    @Mock
    private CertificateChainRepository certificateChainRepositoryMock;

    @Mock
    private InputDataFormatterService inputDataFormatterServiceMock;

    @Mock
    private AuthenticationCertsRepository authenticationCertsRepositoryMock;

    @Mock
    private AuthenticationCerts authenticationCertsMock;

    @Mock
    private PublicKey publicKeyMock;

    private Certificate[] certificateChainMock;

    private byte[] authenticationTokenDataToVerifyMock;

    private String signatureMock = "signature";

    @Mock
    private VoterInformation voterInformationMock;

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

    @Test
    public void givenAuthenticationTokenWhenValidSignatureThenValidationSuccess()
            throws ResourceNotFoundException, GeneralCryptoLibException, CertificateException, IOException {
        String tenantId = "1";
        String electionEventId = "1";
        String votingCardId = "1";

        final KeyPair KEY_PAIR_FOR_SIGNING = CryptoUtils.getKeyPairForSigning();
        final CryptoAPIX509Certificate certificate = CryptoUtils.createCryptoAPIx509Certificate(COMMON_NAME,
            CertificateParameters.Type.SIGN, KEY_PAIR_FOR_SIGNING);
        final String pem = new String(certificate.getPemEncoded(), StandardCharsets.UTF_8);

        final JsonObject json = Json.createObjectBuilder().add("authenticationTokenSignerCert", pem).build();

        when(authenticationCertsMock.getJson()).thenReturn(json.toString());
        when(asymmetricServiceMock.verifySignature(any(byte[].class), any(PublicKey.class), any(byte[].class)))
            .thenReturn(true);

        final AuthenticationToken authenticationToken = BeanUtils.createAuthenticationToken();

        final ValidationResult execute =
            validation.execute(tenantId, electionEventId, votingCardId, authenticationToken);
        assertTrue(execute.isResult());

    }

    @Test
    public void givenAuthenticationTokenInvalidSignatureThenValidationFail()
            throws GeneralCryptoLibException, ResourceNotFoundException, CertificateException, IOException {
        String tenantId = "1";
        String electionEventId = "1";
        String votingCardId = "1";

        final KeyPair keyPairForSigning = CryptoUtils.getKeyPairForSigning();
        expectedException.expect(AuthTokenValidationException.class);
        final AuthenticationToken authenticationToken = BeanUtils.createAuthenticationToken();
        final CryptoAPIX509Certificate certificate =
            CryptoUtils.createCryptoAPIx509Certificate(COMMON_NAME, CertificateParameters.Type.SIGN, keyPairForSigning);
        final String pem = new String(certificate.getPemEncoded(), StandardCharsets.UTF_8);

        final JsonObject json = Json.createObjectBuilder().add("authenticationTokenSignerCert", pem).build();
        when(authenticationCertsMock.getJson()).thenReturn(json.toString());
        when(asymmetricServiceMock.verifySignature(any(byte[].class), any(PublicKey.class), any(byte[].class)))
            .thenReturn(false);
        validation.execute(tenantId, electionEventId, votingCardId, authenticationToken);

    }

    @Before
    public void setUp() throws CryptographicOperationException, ResourceNotFoundException, GeneralCryptoLibException {

        // get public key
        when(signatureServiceMock.getPublicKeyByAliasInCertificateChain(certificateChainMock, KEYSTORE_ALIAS))
            .thenReturn(publicKeyMock);

        // verify signature
        when(authenticationTokenMock.getSignature()).thenReturn(signatureMock);
        when(authenticationCertsMock.getJson())
            .thenReturn("{\"authenticationTokenSignerCert\":\"aafdasdfsfasdfasddfasdf}");

        when(authenticationTokenMock.getId()).thenReturn("id");
        when(authenticationTokenMock.getTimestamp()).thenReturn("timestamp");
        when(authenticationTokenMock.getVoterInformation()).thenReturn(voterInformationMock);
        when(authenticationCertsRepositoryMock.findByTenantIdElectionEventId(any(String.class), any(String.class)))
            .thenReturn(authenticationCertsMock);

        when(inputDataFormatterServiceMock.concatenate(authenticationTokenMock.getId(),
            authenticationTokenMock.getTimestamp(), authenticationTokenMock.getVoterInformation().getTenantId(),
            authenticationTokenMock.getVoterInformation().getElectionEventId(),
            authenticationTokenMock.getVoterInformation().getVotingCardId(),
            authenticationTokenMock.getVoterInformation().getBallotId(),
            authenticationTokenMock.getVoterInformation().getCredentialId(),
            authenticationTokenMock.getVoterInformation().getVerificationCardId(),
            authenticationTokenMock.getVoterInformation().getBallotBoxId(),
            authenticationTokenMock.getVoterInformation().getVerificationCardSetId(),
            authenticationTokenMock.getVoterInformation().getVotingCardSetId()))
                .thenReturn(authenticationTokenDataToVerifyMock);
    }

    @Test
    public void givenAuthenticationTokenCryptoLibExceptionWhenVerifying()
            throws GeneralCryptoLibException, ResourceNotFoundException, CertificateException, IOException {
        String tenantId = "1";
        String electionEventId = "1";
        String votingCardId = "1";

        final KeyPair keyPairForSigning = CryptoUtils.getKeyPairForSigning();
        expectedException.expect(AuthTokenValidationException.class);
        final AuthenticationToken authenticationToken = BeanUtils.createAuthenticationToken();
        final CryptoAPIX509Certificate certificate =
            CryptoUtils.createCryptoAPIx509Certificate(COMMON_NAME, CertificateParameters.Type.SIGN, keyPairForSigning);
        final String pem = new String(certificate.getPemEncoded(), StandardCharsets.UTF_8);

        final JsonObject json = Json.createObjectBuilder().add("authenticationTokenSignerCert", pem).build();
        when(authenticationCertsMock.getJson()).thenReturn(json.toString());
        when(asymmetricServiceMock.verifySignature(any(byte[].class), any(PublicKey.class), any(byte[].class)))
            .thenThrow(new GeneralCryptoLibException("exception"));
        validation.execute(tenantId, electionEventId, votingCardId, authenticationToken);

    }

}
