/*
 * 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.vm.ui.ws.rs.application.operation;

import com.scytl.products.ov.commons.test.CryptoUtils;
import org.apache.commons.lang.math.RandomUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.extension.rest.client.ArquillianResteasyResource;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
import org.jboss.resteasy.client.jaxrs.internal.ClientResponse;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;

import javax.annotation.Resource;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.security.KeyPair;
import java.security.PublicKey;
import java.security.Security;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.products.ov.commons.beans.domain.model.certificate.CertificateEntity;
import com.scytl.products.ov.commons.crypto.configuration.CertificateParameters;
import com.scytl.products.ov.commons.infrastructure.remote.client.RetrofitException;
import com.scytl.products.ov.commons.infrastructure.remote.service.RemoteCertificateService;
import com.scytl.products.ov.commons.tracking.TrackIdInstance;
import com.scytl.products.ov.commons.verify.CSVVerifier;
import com.scytl.products.ov.vm.domain.model.credential.Credential;
import com.scytl.products.ov.vm.infrastructure.persistence.CredentialRepositoryImpl;
import com.scytl.products.ov.vm.infrastructure.remote.VmRemoteCertificateService;
import com.scytl.products.ov.vm.infrastructure.transaction.VoterMaterialTransactionController;
import static org.mockito.Matchers.any;

@RunWith(Arquillian.class)
public class CredentialDataResourceArquillianTest {

    private static final String TENANT_CERTIFICATE_NAME = "Tenant100CA";

    private static final String TEST_PLATFORM_NAME = "Scytl";

    private static final String BASE_PATH = CredentialDataResource.RESOURCE_PATH + "/";

    private static final String VM_DEPLOYMENT_PATH = "vm-ws-rest";

    private static final String TEST_ELECTION_EVENT_ID = "thisisatestelectioneventid";

    private static final String TEST_TENANT_ID = "100";

    private static final String TEST_CREDENTIAL_ID = "thisisatestcredentialid";

    private static final String TEST_DATA = "thisisthetestdata";

    private static final String TEST_VCS_ID = "thisisthevcsid";

    private static final String TEST_ADMIN_BOARD_ID = "1";

    private static final String TEST_ADMIN_BOARD_NAME = "AdministrationBoard " + TEST_ADMIN_BOARD_ID;

    private static final String TEST_CERTIFICATE_NAME = "TestCertificateName" + RandomUtils.nextInt();

    private static final String TEST_CREDENTIAL_DATA_FILE_PATH = "credentialData.csv";

    private static final String TENANT_ID = "{tenantId}";

    private static final String ELECTION_EVENT_ID = "{electionEventId}";

    private static final String VCS_ID = "{votingCardSetId}";

    private static final String AB_ID = "{adminBoardId}";

    private static String SAVE_CREDENTIAL_DATA_PATH = BASE_PATH + CredentialDataResource.SAVE_CREDENTIAL_DATA_PATH;

    private static void resolvePaths() {
        // JAX-RS incompatibility with resolveTemplate method

        SAVE_CREDENTIAL_DATA_PATH = SAVE_CREDENTIAL_DATA_PATH.replace(TENANT_ID, TEST_TENANT_ID)
            .replace(ELECTION_EVENT_ID, TEST_ELECTION_EVENT_ID).replace(VCS_ID, TEST_VCS_ID)
            .replace(AB_ID, TEST_ADMIN_BOARD_ID);
    }

    @PersistenceContext(unitName = "persistenceUnitJdbc")
    EntityManager entityManager;

    @Resource
    UserTransaction userTransaction;

    @Mock
    @Produces
    @VmRemoteCertificateService
    private static RemoteCertificateService remoteCertificateService;

    @Mock
    @Produces
    private static CSVVerifier verifier;

    private CertificateEntity certificateEntity;

    @Before
    public void setupMock() throws IOException, GeneralCryptoLibException, RetrofitException {

        Security.addProvider(new BouncyCastleProvider());

        certificateEntity = getTestCertificateEntity();
        certificateEntity.setCertificateName(TEST_CERTIFICATE_NAME);

        Mockito.when(remoteCertificateService.getAdminBoardCertificate(TEST_ADMIN_BOARD_NAME))
            .thenReturn(certificateEntity);
        Mockito.when(verifier.verify(any(PublicKey.class), any(Path.class))).thenReturn(true);
    }

    @Deployment
    public static WebArchive createDeploymentVoterMaterial() {

        resolvePaths();

        WebArchive war = ShrinkWrap.create(WebArchive.class, VM_DEPLOYMENT_PATH + ".war")
            .addClass(VoterMaterialTransactionController.class).addClass(CredentialRepositoryImpl.class)
            .addClass(TrackIdInstance.class).addClass(CredentialDataResource.class).addClass(BouncyCastleProvider.class)
            .addClass(VmRemoteCertificateService.class).addPackages(true, "org.slf4j")
            .addPackages(true, "com.scytl.products.ov.commons.util").addPackages(true, "com.scytl.products.oscore")
            .addPackages(true, "com.scytl.cryptolib").addPackages(true, "org.h2")
            .addPackages(true, "com.fasterxml.jackson.jaxrs").addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
            .addAsManifestResource("test-persistence.xml", "persistence.xml");

        return war;
    }

    @Test
    @RunAsClient
    public void testSaveCredentialData(@ArquillianResteasyResource("") ResteasyWebTarget webTarget)
            throws IOException, GeneralCryptoLibException, HeuristicRollbackException, HeuristicMixedException,
            NotSupportedException, RollbackException, SystemException {

        try (InputStream testData =
            this.getClass().getClassLoader().getResourceAsStream(TEST_CREDENTIAL_DATA_FILE_PATH)) {

            createAndPersistTestCredential();

            ClientResponse response = (ClientResponse) webTarget.path(SAVE_CREDENTIAL_DATA_PATH)
                .request(MediaType.APPLICATION_JSON_TYPE).post(Entity.entity(testData, "text/csv"));

            Assert.assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
        }

    }

    private Credential createAndPersistTestCredential()
            throws IOException, GeneralCryptoLibException, SystemException, NotSupportedException,
            HeuristicRollbackException, HeuristicMixedException, RollbackException {

        Credential testCredentialEntity = new Credential();
        testCredentialEntity.setElectionEventId(TEST_ELECTION_EVENT_ID);
        testCredentialEntity.setTenantId(TEST_TENANT_ID);
        testCredentialEntity.setCredentialId(TEST_CREDENTIAL_ID);
        testCredentialEntity.setData(TEST_DATA);

        userTransaction.begin();
        entityManager.persist(testCredentialEntity);
        userTransaction.commit();

        return testCredentialEntity;
    }

    private String getCertificateAsString(String cN) throws IOException, GeneralCryptoLibException {

        final KeyPair keyPairForSigning = CryptoUtils.getKeyPairForSigning();
        CryptoAPIX509Certificate certificate =
            CryptoUtils.createCryptoAPIx509Certificate(cN, CertificateParameters.Type.SIGN, keyPairForSigning);

        return new String(certificate.getPemEncoded(), StandardCharsets.UTF_8);
    }

    private CertificateEntity getTestCertificateEntity() throws IOException, GeneralCryptoLibException {

        String testCertificate = getCertificateAsString(TENANT_CERTIFICATE_NAME);
        CertificateEntity testCertificateEntity = new CertificateEntity();
        testCertificateEntity.setCertificateContent(testCertificate);
        testCertificateEntity.setCertificateName(TEST_CERTIFICATE_NAME);
        testCertificateEntity.setElectionEventId(TEST_ELECTION_EVENT_ID);
        testCertificateEntity.setPlatformName(TEST_PLATFORM_NAME);
        testCertificateEntity.setTenantId(TEST_TENANT_ID);

        return testCertificateEntity;
    }

}
