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

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.cert.CertificateException;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
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 org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.products.ov.commons.beans.domain.model.platform.PlatformInstallationData;
import com.scytl.products.ov.commons.beans.domain.model.tenant.TenantInstallationData;
import com.scytl.products.ov.commons.infrastructure.health.HealthCheckStatus;
import com.scytl.products.ov.commons.infrastructure.health.HealthCheckValidationType;
import com.scytl.products.ov.cr.services.domain.model.certificate.CertificateEntity;
import com.scytl.products.ov.cr.ui.ws.rs.operation.certificate.CryptoCreator;

import cucumber.api.CucumberOptions;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import cucumber.runtime.arquillian.ArquillianCucumber;
import cucumber.runtime.arquillian.api.Features;

@RunAsClient
@RunWith(ArquillianCucumber.class)
@Features({"features/cr_functionality.feature" })
@CucumberOptions(features = "features", tags = {"~@ignore" }, plugin = {"pretty", "html:target/site/cucumber-pretty",
        "json:target/cucumber.json" })
public class CertificateRegistryResourceITest {

    private static final String SAVE_TENANT_CERT_PATH =
        CrTenantDataResource.RESOURCE_PATH + "/" + CrTenantDataResource.SAVE_TENANT_DATA;

    private static final String CHECK_TENANT_CERT_PATH =
        CrTenantDataResource.RESOURCE_PATH + CrTenantDataResource.GET_TENANT_CA_CERTIFICATE;

    private static final String SAVE_ELECTION_CERT_PATH = CertificateRegistryResource.RESOURCE_PATH + "/"
        + CertificateRegistryResource.SAVE_CERTIFICATE_FOR_ELECTION_EVENT;

    private static final String GET_ELECTION_CERT_PATH =
        CertificateRegistryResource.RESOURCE_PATH + "/" + CertificateRegistryResource.GET_CERTIFICATE_FOR_TENANT_EEID;

    private static final String SAVE_TENANT_CERT_WITH_NAME_PATH =
        CertificateRegistryResource.RESOURCE_PATH + "/" + CertificateRegistryResource.SAVE_CERTIFICATE_FOR_TENANT;

    private static final String CHECK_TENANT_CERT_WITH_NAME_PATH =
        CertificateRegistryResource.RESOURCE_PATH + "/" + CertificateRegistryResource.CHECK_IF_CERTIFICATE_EXIST;

    private static final String GET_TENANT_CERT_WITH_NAME_PATH =
        CertificateRegistryResource.RESOURCE_PATH + "/" + CertificateRegistryResource.GET_CERTIFICATE_FOR_TENANT;

    private static final String SAVE_CERT_PATH =
        CertificateRegistryResource.RESOURCE_PATH + "/" + CertificateRegistryResource.SAVE_CERTIFICATE;

    private static final String GET_CERT_PATH =
        CertificateRegistryResource.RESOURCE_PATH + "/" + CertificateRegistryResource.GET_CERTIFICATE;

    private static final String TENANT_ID = "tenantId";

    private static final String ELECTION_EVENT_ID = "electionEventId";

    private static final String CERTIFICATE_NAME = "certificateName";

    @ArquillianResource
    URL url;

    @Resource
    UserTransaction userTransaction;

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

    @ClassRule
    public static final TemporaryFolder TEMP_FOLDER = new TemporaryFolder();

    private Response response;

    private PlatformInstallationData platformInstallationData;

    private CryptoCreator certificates = new CryptoCreator();

    private CertificateEntity certificate;

    private static final SystemPropertiesLoader spl = new SystemPropertiesLoader();

    @Before
    public void init()
            throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException,
            RollbackException {
        userTransaction.begin();
        Query query = entityManager.createQuery("DELETE FROM " + CertificateEntity.class.getSimpleName());
        query.executeUpdate();
        userTransaction.commit();
    }

    @BeforeClass
    public static void config() throws Exception {

        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
        spl.setProperties();
    }

    @Given("^that the service is up and running, but not configured$")
    public void checkPlatformIsNotconfigured() throws Exception {
        final Response response =
            webTarget().path(CheckHealthResource.RESOURCE_PATH).request(MediaType.APPLICATION_JSON_TYPE).get();
        assertEquals(Response.Status.SERVICE_UNAVAILABLE.getStatusCode(), response.getStatus());
        HealthCheckStatus readEntity = response.readEntity(HealthCheckStatus.class);
        assertFalse(readEntity.getResults().get(HealthCheckValidationType.LOGGING_INITIALIZED).getHealthy());
    }

    @Given("^that the service is up and running$")
    public void checkIfSystemIsUp() {
        final Response response =
            webTarget().path(CheckHealthResource.RESOURCE_PATH).request(MediaType.APPLICATION_JSON_TYPE).get();
        assertFalse(Response.Status.SERVICE_UNAVAILABLE.getStatusCode() == response.getStatus());
    }

    @Given("^there is no tenant configured with id (\\d+)$")
    public void checkIfTenantCertificateExist(int tenantId) {
        final Response response = webTarget().path(CHECK_TENANT_CERT_PATH).resolveTemplate(TENANT_ID, tenantId)
            .request(MediaType.APPLICATION_JSON_TYPE).get();
        assertFalse(Response.Status.OK.getStatusCode() == response.getStatus());
    }

    @Given("^there is no election configured with name (\\d+)$")
    public void checkIfElectionCertificateExist(int tenantId) {
        final Response response = webTarget().path(CHECK_TENANT_CERT_PATH).resolveTemplate(TENANT_ID, tenantId)
            .request(MediaType.APPLICATION_JSON_TYPE).get();
        assertFalse(Response.Status.OK.getStatusCode() == response.getStatus());
    }

    @Given("^that the certificate with name (.+) is available in the repository$")
    public void checkIfCertificateWithNameExist(String certificateName) {
        final Response response = webTarget().path(GET_CERT_PATH).resolveTemplate(CERTIFICATE_NAME, certificateName)
            .request(MediaType.APPLICATION_JSON_TYPE).get();
        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
    }

    @Given("^that tenant (\\d+) and certificate with name (.+) is available in the repository$")
    public void checkIfCertificateWithNameExistForTenant(int tenantId, String certificateName) {
        final Response response =
            webTarget().path(CHECK_TENANT_CERT_WITH_NAME_PATH).resolveTemplate(TENANT_ID, tenantId)
                .resolveTemplate(CERTIFICATE_NAME, certificateName).request(MediaType.APPLICATION_JSON_TYPE).get();
        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
    }

    @Given("^that tenant (\\d+), election id (\\d+) and certificate with name (.+) are available in the repository$")
    public void checkIfCertificateWithNameExistForTenantAndElection(int tenantId, int electionId,
            String certificateName) {
        response = webTarget().path(CHECK_TENANT_CERT_WITH_NAME_PATH).resolveTemplate(TENANT_ID, tenantId)
            .resolveTemplate(ELECTION_EVENT_ID, electionId).resolveTemplate(CERTIFICATE_NAME, certificateName)
            .request(MediaType.APPLICATION_JSON_TYPE).get();
        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
    }

    @When("^uploading tenant certificate for tenant with id (\\d+)$")
    public void uploadTenantCertificate(int tenantId)
            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, URISyntaxException,
            OperatorCreationException {
        TenantInstallationData tenantInstallationData = new TenantInstallationData();
        tenantInstallationData.setEncodedData(certificates.createTenantInstallationData(tenantId));
        final Response response = webTarget().path(SAVE_TENANT_CERT_PATH).resolveTemplate(TENANT_ID, tenantId)
            .request(MediaType.APPLICATION_JSON_TYPE)
            .post(Entity.entity(tenantInstallationData, MediaType.APPLICATION_JSON_TYPE));
        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
    }

    @When("^saving a tenant (\\d+), election id (\\d+) and certificate with name (.+) in the repository$")
    public void uploadCertificateWithNameToTenantAndElection(int tenantId, int electionId, String certName)
            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, URISyntaxException,
            OperatorCreationException, GeneralCryptoLibException {
        CertificateEntity ce = new CertificateEntity();
        ce.setCertificateName(certName);
        ce.setElectionEventId(Integer.toString(electionId));
        ce.setTenantId(Integer.toString(tenantId));
        ce.setCertificateContent(certificates.createElectionCertificate(certName));
        ce.setPlatformName("Organization");
        response = webTarget().path(SAVE_ELECTION_CERT_PATH).resolveTemplate(TENANT_ID, tenantId)
            .resolveTemplate(ELECTION_EVENT_ID, electionId).request(MediaType.APPLICATION_JSON_TYPE)
            .post(Entity.entity(ce, MediaType.APPLICATION_JSON_TYPE));
    }

    @When("^saving a tenant (\\d+) and certificate with name (.+) in the repository$")
    public void uploadCertificateWithNameToTenant(int tenantId, String certName)
            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, URISyntaxException,
            OperatorCreationException, GeneralCryptoLibException {
        CertificateEntity ce = new CertificateEntity();
        ce.setCertificateName(certName);
        ce.setTenantId(Integer.toString(tenantId));
        ce.setCertificateContent(certificates.createElectionCertificate(certName));
        ce.setPlatformName("Organization");
        response = webTarget().path(SAVE_TENANT_CERT_WITH_NAME_PATH).resolveTemplate(TENANT_ID, tenantId)
            .request(MediaType.APPLICATION_JSON_TYPE).post(Entity.entity(ce, MediaType.APPLICATION_JSON_TYPE));
        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
    }

    @When("^saving a certificate with name (.+) in the repository$")
    public void uploadCertificateWithName(String certName)
            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, URISyntaxException,
            OperatorCreationException, GeneralCryptoLibException {
        CertificateEntity ce = new CertificateEntity();
        ce.setCertificateName(certName);
        ce.setCertificateContent(certificates.createElectionCertificate(certName));
        ce.setPlatformName("Organization");
        response = webTarget().path(SAVE_CERT_PATH).request(MediaType.APPLICATION_JSON_TYPE)
            .post(Entity.entity(ce, MediaType.APPLICATION_JSON_TYPE));
    }

    @When("^upon configuring the plaform$")
    public void uploadPlatformCertificate()
            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
        platformInstallationData = certificates.createPlatformInstallationData();
        final Response response =
            webTarget().path(CrPlatformDataResource.RESOURCE_PATH).request(MediaType.APPLICATION_JSON_TYPE)
                .post(Entity.entity(platformInstallationData, MediaType.APPLICATION_JSON_TYPE));
        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
    }

    @When("^downloading a certificate with name (.+)$")
    public void retrieveCertificate(String certificateName) throws Exception {
        response = webTarget().path(GET_CERT_PATH).resolveTemplate(CERTIFICATE_NAME, certificateName)
            .request(MediaType.APPLICATION_JSON_TYPE).get();
        if (Response.Status.OK.getStatusCode() == response.getStatus()) {
            certificate = response.readEntity(CertificateEntity.class);
        } else {
            certificate = null;
        }
    }

    @When("^health check is performed$")
    public void performHealthCheck() {
        response = webTarget().path(CheckHealthResource.RESOURCE_PATH).request(MediaType.APPLICATION_JSON_TYPE).get();
    }

    @Then("^the service replies with successfull healthcheck$")
    public void checkSuccessfullHealthCheck() throws Exception {
        final Response response =
            webTarget().path(CheckHealthResource.RESOURCE_PATH).request(MediaType.APPLICATION_JSON_TYPE).get();
        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
    }

    @Then("^the system returns (\\d+)$")
    public void checkHealthCheck(int responseStatus) throws Exception {
        assertEquals(responseStatus, response.getStatus());
    }

    @Then("^certificate with name (.+) can be saved$")
    public void checkSuccessfulCertificateUpload(String certificateName) throws Exception {
        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
    }

    @Then("^tenant (\\d+) and certificate with name (.+) can be saved$")
    public void checkSuccessfulCertificateUploadForTenant(int tenantId, String certificateName) throws Exception {
        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
    }

    @Then("^the certificate for tenant with id (\\d+) can be retrieved$")
    public void retrieveCertificateForTenant(int tenantId) throws Exception {
        final Response response = webTarget().path(CHECK_TENANT_CERT_PATH).resolveTemplate(TENANT_ID, tenantId)
            .request(MediaType.APPLICATION_JSON_TYPE).get();
        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
    }

    @Then("^downloading the certificate for tenant with id (\\d+)$")
    public void downloadCertificateForTenant(int tenantId) throws Exception {
        response = webTarget().path(CHECK_TENANT_CERT_PATH).resolveTemplate(TENANT_ID, tenantId)
            .request(MediaType.APPLICATION_JSON_TYPE).get();
    }

    @Then("^downloading a tenant (\\d+) and certificate (.+) with name$")
    public void downloadCertificateForTenant(int tenantId, String certName) throws Exception {
        response = webTarget().path(GET_TENANT_CERT_WITH_NAME_PATH).resolveTemplate(TENANT_ID, tenantId)
            .resolveTemplate(CERTIFICATE_NAME, certName).request(MediaType.APPLICATION_JSON_TYPE).get();
    }

    @When("^downloading the certificate for tenant (\\d+) and election id (\\d+) with name (.+)$")
    public void downloadCertificateForTenantAndElectionWithName(int tenantId, int electionId, String certName)
            throws Exception {
        response = webTarget().path(GET_ELECTION_CERT_PATH).resolveTemplate(ELECTION_EVENT_ID, electionId)
            .resolveTemplate(TENANT_ID, tenantId).resolveTemplate(CERTIFICATE_NAME, certName)
            .request(MediaType.APPLICATION_JSON_TYPE).get();
    }

    @Then("^system returns an error on retrieving a certificate$")
    public void checkReturnError() throws Exception {
        assertNotEquals(Response.Status.OK.getStatusCode(), response.getStatus());
    }

    @Then("^system returns an error on retrieving a tenant (\\d+) certificate$")
    public void checkTenantCertificateReturnError(int tenantId) throws Exception {
        assertNotEquals(Response.Status.OK.getStatusCode(), response.getStatus());
    }

    @Then("^system returns an error on retrieving the certificate for tenant (\\d+) and election id (\\d+) with name (.+)$")
    public void checkReturnErrorForTenantAndElectionWithCertificateName(int tenantId, int electionId, String certName)
            throws Exception {
        assertNotEquals(Response.Status.OK.getStatusCode(), response.getStatus());
    }

    @Then("^the tenant (\\d+) and certificate with name (.+) can be retrieved$")
    public void checkReturnErrorForTenantWithCertificateName(int tenantId, String certName) throws Exception {
        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
    }

    @Then("^tenant (\\d+), election id (\\d+) and certificate with name (.+) can be saved$")
    public void checkReturnForTenantAndElectionWithCertificateName(int tenantId, int electionId, String certName)
            throws Exception {
        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
    }

    @Then("^the certificate with name can (.+) be retrieved$")
    public void retrieveCertificateWithName(String certName) throws Exception {
        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
        assertEquals(certificate.getCertificateName(), certName);
    }

    @Then("^the certificate for tenant (\\d+) election (\\d+) and with name (.+) can be retrieved$")
    public void retrieveCertificateWithNameToTenantAndElection(int tenantId, int electionId, String certName)
            throws Exception {

        final Response response = webTarget().path(GET_ELECTION_CERT_PATH)
            .resolveTemplate(ELECTION_EVENT_ID, electionId).resolveTemplate(TENANT_ID, tenantId)
            .resolveTemplate(CERTIFICATE_NAME, certName).request(MediaType.APPLICATION_JSON_TYPE).get();
        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
        CertificateEntity retrieved = response.readEntity(CertificateEntity.class);
        assertEquals(retrieved.getCertificateName(), certName);
    }

    /**
     * Gets a Resteasy webTarget.
     */
    private ResteasyWebTarget webTarget() {
        final ResteasyClient client = new ResteasyClientBuilder().build();
        return client.target(url.toString());
    }
}
