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

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.Security;

import javax.annotation.Resource;
import javax.json.Json;
import javax.json.JsonObjectBuilder;
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.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import static org.hamcrest.core.Is.is;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.Filters;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.After;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.metatype.sxc.util.Base64;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.scytl.products.ov.au.services.domain.model.adminboard.AdminBoard;
import com.scytl.products.ov.au.services.domain.model.authentication.AuthenticationCerts;
import com.scytl.products.ov.au.services.domain.model.authentication.AuthenticationContentEntity;
import com.scytl.products.ov.au.services.infrastructure.remote.AuRemoteCertificateService;
import com.scytl.products.ov.au.services.infrastructure.remote.AuRemoteCertificateServiceImpl;
import com.scytl.products.ov.commons.beans.domain.model.certificate.CertificateEntity;
import com.scytl.products.ov.commons.infrastructure.remote.service.RemoteCertificateService;

@RunWith(Arquillian.class)
@RunAsClient
public class AuthenticationArquillianTest {

    private final int STATUS_OK = 200;

    private final String AUTH_CERTS_JSON = "{ }";

    private final String AUTH_PARAMS_CHALLENGE_EXP_TIME = "1";

    private final String AUTH_PARAMS_TOKEN_EXP_TIME = "2";

    private final String AUTH_PARAMS_CHALLENGE_LENGTH = "3";

    private final String AUTH_TOKEN_SIGNER_PASSWORD = "4";

    private final String AUTH_TOKEN_SIGNER_KEYSTORE_JSON = "{ }";

    private static final String ADMIN_BOARD_CERTIFICATE_CONTENT = "6";

    private static final String TENANT_CERTIFICATE_CONTENT = "7";

    private final String ADMIN_BOARD_ID = "8";

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

    @Resource
    UserTransaction userTransaction;

    private final String TENANT_ID = "100";

    private final String ELECTION_EVENT_ID = "123";

    private final String GET_AUTHENTICATION_INFORMATION_PATH =
        AuthenticationDataResource.RESOURCE_PATH + "/" + AuthenticationDataResource.GET_AUTHENTICATION_INFORMATION_PATH;

    @Deployment
    public static WebArchive deploy() {
        return ShrinkWrap.create(WebArchive.class, "au-ws-rest.war").addClasses(org.slf4j.Logger.class)
            .addPackage("com.scytl.products.oscore").addPackage("com.scytl.cryptolib").addPackages(true, "org.h2")
            .addPackages(true, "com.fasterxml.jackson.jaxrs")
            .addPackages(true, Filters.exclude(".*Test.*"), "com.scytl.products.ov.au")
            .addPackages(true, "com.scytl.products.ov.commons.logging")
            .addPackages(true, "com.scytl.products.ov.commons.tracking")
            .addPackages(true, "com.scytl.products.ov.commons.crypto")
            .addPackages(true, "com.scytl.products.ov.commons.util").addClasses(BouncyCastleProvider.class)
            .deleteClass(AuRemoteCertificateServiceImpl.class).addAsResource("log4j.xml")
            .addAsManifestResource("test-persistence.xml", "persistence.xml").addAsWebInfResource("beans.xml");
    }

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

    @Test
    public void testGetAuthenticationInformation_Success(@ArquillianResource URL baseUrl)
            throws SecurityException, IllegalStateException, NotSupportedException, SystemException, RollbackException,
            HeuristicMixedException, HeuristicRollbackException, URISyntaxException, JsonProcessingException,
            IOException {
        saveMockAuthenticationInformation();

        Client client = ClientBuilder.newClient();
        WebTarget webTarget = client.target(baseUrl.toString());

        Response response = webTarget.path(GET_AUTHENTICATION_INFORMATION_PATH)
            .resolveTemplate(AuthenticationDataResource.PARAMETER_VALUE_TENANT_ID, TENANT_ID)
            .resolveTemplate(AuthenticationDataResource.PARAMETER_VALUE_ELECTION_EVENT_ID, ELECTION_EVENT_ID)
            .request(MediaType.APPLICATION_JSON).get();

        Assert.assertThat(response.getStatus(), is(STATUS_OK));

        JsonNode authData = new ObjectMapper().readTree(response.readEntity(String.class));

        Assert.assertThat(authData.get("authenticationContent").get("challengeExpirationTime").asText(),
            is(AUTH_PARAMS_CHALLENGE_EXP_TIME));
    }

    @After
    public void cleanup()
            throws NotSupportedException, SystemException, SecurityException, IllegalStateException, RollbackException,
            HeuristicMixedException, HeuristicRollbackException {
        userTransaction.begin();
        entityManager.createQuery("delete from AuthenticationContentEntity").executeUpdate();
        entityManager.createQuery("delete from AuthenticationCerts").executeUpdate();
        entityManager.createQuery("delete from AdminBoard").executeUpdate();
        userTransaction.commit();
    }

    private void saveMockAuthenticationInformation()
            throws NotSupportedException, SystemException, SecurityException, IllegalStateException, RollbackException,
            HeuristicMixedException, HeuristicRollbackException {

        AuthenticationContentEntity authContent = new AuthenticationContentEntity();
        authContent.setElectionEventId(ELECTION_EVENT_ID);
        authContent.setTenantId(TENANT_ID);

        JsonObjectBuilder jsonBuilder = Json.createObjectBuilder();
        JsonObjectBuilder jsonAuthParams = Json.createObjectBuilder();
        jsonAuthParams.add("challengeResExpTime", AUTH_PARAMS_CHALLENGE_EXP_TIME);
        jsonAuthParams.add("authTokenExpTime", AUTH_PARAMS_TOKEN_EXP_TIME);
        jsonAuthParams.add("challengeLength", AUTH_PARAMS_CHALLENGE_LENGTH);
        jsonBuilder.add("authenticationParams", jsonAuthParams.build());
        jsonBuilder.add("authenticationTokenSignerPassword", AUTH_TOKEN_SIGNER_PASSWORD);
        jsonBuilder.add("authenticationTokenSignerKeystore", Base64.encode(AUTH_TOKEN_SIGNER_KEYSTORE_JSON.getBytes()));

        authContent.setJson(jsonBuilder.build().toString());

        AuthenticationCerts authCerts = new AuthenticationCerts();
        authCerts.setElectionEventId(ELECTION_EVENT_ID);
        authCerts.setTenantId(TENANT_ID);
        authCerts.setJson(AUTH_CERTS_JSON);

        AdminBoard adminBoard = new AdminBoard();
        adminBoard.setElectionEventId(ELECTION_EVENT_ID);
        adminBoard.setTenantId(TENANT_ID);
        adminBoard.setAdminBoardId(ADMIN_BOARD_ID);

        userTransaction.begin();
        entityManager.persist(authContent);
        entityManager.persist(authCerts);
        entityManager.persist(adminBoard);
        userTransaction.commit();
    }

    @AuRemoteCertificateService
    public static class RemoteCertificateServiceMock implements RemoteCertificateService {
        @Override
        public CertificateEntity getAdminBoardCertificate(String id) {
            CertificateEntity certEntity = new CertificateEntity();
            certEntity.setCertificateContent(ADMIN_BOARD_CERTIFICATE_CONTENT);

            return certEntity;
        }

        @Override
        public CertificateEntity getTenantCACertificate(String tenantId) {
            CertificateEntity certEntity = new CertificateEntity();
            certEntity.setCertificateContent(TENANT_CERTIFICATE_CONTENT);

            return certEntity;
        }

    }

}
