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

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
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.util.Base64;

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.Matchers;
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.asymmetric.service.AsymmetricService;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.ov.au.services.domain.model.challenge.ChallengeInformation;
import com.scytl.products.ov.au.services.domain.model.challenge.ClientChallengeMessage;
import com.scytl.products.ov.au.services.domain.model.challenge.ServerChallengeMessage;
import com.scytl.products.ov.au.services.domain.service.authenticationcontent.AuthenticationContentService;
import com.scytl.products.ov.commons.beans.domain.model.authentication.AuthenticationContent;
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.tracking.TrackIdInstance;

@RunWith(MockitoJUnitRunner.class)
public class ChallengeServerInformationSignatureValidationTest {

    public static final String TENANT_ID = "1";

    public static final String ELECTION_EVENT_ID = "1";

    public static final String VOTING_CARD_ID = "1";

    public static final String EMPTY_STRING = "";

    public static final String CHALLENGE_MESSAGE = "message";

    @InjectMocks
    ChallengeServerInformationSignatureValidation validation = new ChallengeServerInformationSignatureValidation();

    @Mock
    ChallengeInformation challengeInformationMock;

    @Mock
    AuthenticationContentService authenticationContentServiceMock;

    @Mock
    AuthenticationContent authenticationContentMock;

    @Mock
    ServerChallengeMessage serverChallengeMessageMock;

    @Mock
    ClientChallengeMessage clientChallengeMessageMock;

    @Mock
    InputDataFormatterService inputDataFormatterService;

    @Mock
    SecureLoggingWriter secureLoggingWriter;

    @Mock
    CertificateChainRepository certificateChainRepository;

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

    private PublicKey publicKeyMock;

    @Mock
    private SignatureForObjectService signatureForObjectService;

    @Mock
    private TrackIdInstance trackIdInstance;

    @Mock
    private Logger logger;

    private static AsymmetricServiceAPI notMockedAsymmetricService;

    private byte[] dataConcatenatedMock = "data".getBytes(StandardCharsets.UTF_8);

    @Mock
    AsymmetricServiceAPI asymmetricServiceMock;

    @BeforeClass
    public static void setUp() throws GeneralCryptoLibException {
        Security.addProvider(new BouncyCastleProvider());
        notMockedAsymmetricService = new AsymmetricService();
    }

    @Before
    public void setup() {
        when(challengeInformationMock.getServerChallengeMessage()).thenReturn(serverChallengeMessageMock);
    }

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

        commonPreparation();
        when(clientChallengeMessageMock.getSignature()).thenReturn(fakeSignatureServer());
        when(serverChallengeMessageMock.getSignature()).thenReturn(fakeSignatureServer());

        when(signatureForObjectService.getPublicKeyByAliasInCertificateChain(any(), anyString()))
            .thenReturn(publicKeyMock);
        when(asymmetricServiceMock.verifySignature(Matchers.any(byte[].class), Matchers.any(PublicKey.class),
            Matchers.any(byte[].class))).thenReturn(true);

        assertTrue(validation.execute(TENANT_ID, ELECTION_EVENT_ID, VOTING_CARD_ID, challengeInformationMock));
    }

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

        commonPreparation();
        when(clientChallengeMessageMock.getSignature()).thenReturn(fakeSignatureServer());
        when(serverChallengeMessageMock.getSignature()).thenReturn(fakeSignatureServer());
        when(signatureForObjectService.getPublicKeyByAliasInCertificateChain(any(), anyString()))
            .thenReturn(publicKeyMock);
        when(asymmetricServiceMock.verifySignature(Matchers.any(byte[].class), Matchers.any(PublicKey.class),
            Matchers.any(byte[].class))).thenReturn(false);
        assertFalse(validation.execute(TENANT_ID, ELECTION_EVENT_ID, VOTING_CARD_ID, challengeInformationMock));
    }

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

        expectedException.expect(CryptographicOperationException.class);
        commonPreparation();
        when(clientChallengeMessageMock.getSignature()).thenReturn(fakeSignatureServer());
        when(serverChallengeMessageMock.getSignature()).thenReturn(fakeSignatureServer());
        when(signatureForObjectService.getPublicKeyByAliasInCertificateChain(any(), anyString()))
            .thenReturn(publicKeyMock);
        when(asymmetricServiceMock.verifySignature(Matchers.any(byte[].class), Matchers.any(PublicKey.class),
            Matchers.any(byte[].class))).thenThrow(new GeneralCryptoLibException("exception"));
        validation.execute(TENANT_ID, ELECTION_EVENT_ID, VOTING_CARD_ID, challengeInformationMock);
    }

    private void commonPreparation() throws IOException, GeneralCryptoLibException {

        final KeyPair keyPair = CryptoUtils.getKeyPairForSigning();
        publicKeyMock = notMockedAsymmetricService.getKeyPairForSigning().getPublic();
        final CryptoAPIX509Certificate certificate =
            CryptoUtils.createCryptoAPIx509Certificate("commonName", CertificateParameters.Type.SIGN, keyPair);
        final String pem = new String(certificate.getPemEncoded(), StandardCharsets.UTF_8);
        publicKeyMock = notMockedAsymmetricService.getKeyPairForSigning().getPublic();
        when(challengeInformationMock.getCertificate()).thenReturn(pem);
        when(challengeInformationMock.getClientChallengeMessage()).thenReturn(clientChallengeMessageMock);
        when(challengeInformationMock.getServerChallengeMessage()).thenReturn(serverChallengeMessageMock);
        when(clientChallengeMessageMock.getClientChallenge()).thenReturn(CHALLENGE_MESSAGE);
        when(serverChallengeMessageMock.getServerChallenge()).thenReturn(CHALLENGE_MESSAGE);
        when(inputDataFormatterService.concatenate(Matchers.anyVararg())).thenReturn(dataConcatenatedMock);
        when(trackIdInstance.getTrackId()).thenReturn(EMPTY_STRING);
    }

    private String fakeSignatureServer() {

        return Base64.getEncoder().encodeToString("signatureclient".getBytes(StandardCharsets.UTF_8));

    }

}
