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

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.UserTransaction;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;

import org.apache.commons.lang3.StringUtils;
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.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;

import com.scytl.products.ov.commons.beans.domain.model.Information.VoterInformation;
import com.scytl.products.ov.commons.beans.domain.model.authentication.AuthenticationToken;
import com.scytl.products.ov.commons.beans.domain.model.confirmation.ConfirmationMessage;
import com.scytl.products.ov.commons.random.impl.SecureRandomBytes;
import com.scytl.products.ov.commons.test.CryptoUtils;
import com.scytl.products.ov.commons.ui.ws.rs.ObjectMappers;
import com.scytl.products.ov.commons.util.DateUtils;
import com.scytl.products.ov.commons.util.JsonUtils;
import com.scytl.products.ov.ei.services.domain.model.ballotbox.BallotBoxInformation;
import com.scytl.products.ov.ei.services.domain.model.confirmation.ConfirmationInformation;
import com.scytl.products.ov.ei.services.domain.model.content.ElectionInformationContent;

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

    private static final String AUTHENTICATION_TOKEN = "authenticationToken";

    private static final String TEST_SIGNATURE = "thisisthetestsignature";

    private static final String URL_VALIDATE_CONFIRMATION_MESSAGE =
        "/confirmations/tenant/{tenantId}/electionevent/{electionEventId}/votingcard/{votingCardId}";

    private static final String TEST_BALLOT_BOX_ID = "7f59b097dcc6407e933fdfa94b9812b7";

    private static final String TEST_BALLOT_ID = "ballotid";

    private static final String TEST_ELECTION_EVENT_ID = "4bbd2887e9a848928487b99490c2296e";

    private static final String TEST_VOTING_CARD_SET_ID = "thevotingcardsetid";

    private static final String TEST_CREDENTIAL_ID = "thisisthecredentialid";

    private static final String TEST_VERIFICATION_CARD_ID = "thisistheverificationcardid";

    private static final String TEST_VERIFICATION_CARD_SET_ID = "thisistheverificationcardsetid";

    private static final String TEST_VOTING_CARD_ID = "31a32fbcac864b8ea96c48a505437721";

    private static final String TEST_ID = "79c6c49af4044b1abb5434413304748f";

    private static final String TRACK_ID_HEADER = "X-Request-ID";

    private static final String TEST_TRACK_ID = "TestTrackingId";

    private static final String BALLOT_BOX_TEMPLATE = "ballotBoxId";

    private static final String TEST_TENANT_ID = "100";

    private static final String ELECTION_EVENT_TEMPLATE = "electionEventId";

    private static final String TENANT_TEMPLATE = "tenantId";

    private static final String VOTING_CARD_ID_TEMPLATE = "votingCardId";

    private static String testConfirmationKey = "0000001";

    private static KeyPair keyPairForSigning;

    private static String testFinalCertificate;

    private static String testRootCACertificateJson;

    private static String TEST_P_VALUE;

    private static String TEST_Q_VALUE;

    private static String TEST_G_VALUE;

    private static Map<String, Object> templates;

    static {
        templates = new HashMap<>();
        templates.put(TENANT_TEMPLATE, TEST_TENANT_ID);
        templates.put(ELECTION_EVENT_TEMPLATE, TEST_ELECTION_EVENT_ID);
        templates.put(VOTING_CARD_ID_TEMPLATE, TEST_VOTING_CARD_ID);
        templates.put(BALLOT_BOX_TEMPLATE, TEST_BALLOT_BOX_ID);
    }

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

    @Resource
    UserTransaction userTransaction;

    @BeforeClass
    public static void configure() throws Exception {

        TEST_P_VALUE = "163705189943195867603197915262935353275764386467821394198460041808371"
            + "0352712903595474204359060942136966594474658788581492085169454645689176764494545912442255376341658651533997"
            + "8014154452159687109161090635367600349264934924141746082060353483306855352192358732451955232000593777554431"
            + "7989815745298543146510920864884263907768113671250095513460893193151115092773471174671079140736394568051590"
            + "9456259395419596053113605220801934339290681600101748805136651812240481996720460142730426738023826391389265"
            + "8950281593755894747339126531018026798982785331079065126375455293409065540731646939808640273393855256230820"
            + "509217411510058759";
        TEST_Q_VALUE = "818525949715979338015989576314676766378821932339106970992300209041855"
            + "1763564517977371021795304710684832972373293942907460425847273228445883822472729562211276881708293257669989"
            + "0070772260798435545805453176838001746324674620708730410301767416534276760961793662259776160002968887772158"
            + "9949078726492715732554604324421319538840568356250477567304465965755575463867355873355395703681972840257954"
            + "7281296977097980265568026104009671696453408000508744025683259061202409983602300713652133690119131956946329"
            + "4751407968779473736695632655090133994913926655395325631877276467045327703658234699043201366969276281154102"
            + "54608705755029379";
        TEST_G_VALUE = "2";

        testConfirmationKey = Base64.getEncoder().encodeToString(testConfirmationKey.getBytes(StandardCharsets.UTF_8));

        prepareCryptoMaterial();
    }

    @Test
    @Ignore // Fails only locally on Maven -- to be investigated at a later
            // point.
    public void testPostConfirmationMessageAndReceiveValidResponse(
            @ArquillianResteasyResource("") ResteasyWebTarget webTarget) throws Exception {

        VoterInformation testVoterInformation = new VoterInformation();
        testVoterInformation.setBallotBoxId(TEST_BALLOT_BOX_ID);
        testVoterInformation.setCredentialId(TEST_CREDENTIAL_ID);
        testVoterInformation.setElectionEventId(TEST_ELECTION_EVENT_ID);
        testVoterInformation.setTenantId(TEST_TENANT_ID);
        testVoterInformation.setBallotId(TEST_BALLOT_ID);
        testVoterInformation.setVotingCardId(TEST_VOTING_CARD_ID);
        testVoterInformation.setVerificationCardId(TEST_VERIFICATION_CARD_ID);
        testVoterInformation.setVerificationCardSetId(TEST_VERIFICATION_CARD_SET_ID);
        testVoterInformation.setVotingCardSetId(TEST_VOTING_CARD_SET_ID);

        AuthenticationToken authenticationToken =
            createAndSignAuthenticationToken(testVoterInformation, keyPairForSigning.getPrivate());
        String authenticationTokenJsonString = ObjectMappers.toJson(authenticationToken);

        ConfirmationInformation confirmationInformation = buildAndSaveEntities(authenticationToken);

        ClientResponse response = (ClientResponse) webTarget.path(URL_VALIDATE_CONFIRMATION_MESSAGE)
            .resolveTemplates(templates).request(MediaType.APPLICATION_JSON).header(TRACK_ID_HEADER, TEST_TRACK_ID)
            .header(AUTHENTICATION_TOKEN, authenticationTokenJsonString)
            .post(Entity.entity(confirmationInformation, MediaType.APPLICATION_JSON_TYPE));

        String jsonString = response.readEntity(String.class);
        JsonObject json = JsonUtils.getJsonObject(jsonString);

        boolean valid = json.getBoolean("valid");

        assertThat(Boolean.TRUE, is(valid));

    }

    private static void prepareCryptoMaterial() throws Exception {
        CryptoUtils.CryptoMaterialContainer cryptoMaterialContainer =
            CryptoUtils.createRootAndChildCertificates(TEST_CREDENTIAL_ID);
        keyPairForSigning = cryptoMaterialContainer.getSubjectKeyPair();
        testFinalCertificate = cryptoMaterialContainer.getSubjectCertificate();
        testRootCACertificateJson = cryptoMaterialContainer.getRootCACertificate();
    }

    private AuthenticationToken createAndSignAuthenticationToken(final VoterInformation voterInformation,
            final PrivateKey privateKey) throws Exception {

        String base64AuthenticationTokenId =
            Base64.getEncoder().encodeToString(SecureRandomBytes.getInstance().getRandomValue(16));
        String currentTimestamp = DateUtils.getTimestamp();

        byte[] tokenSignature = CryptoUtils.sign(privateKey, base64AuthenticationTokenId, currentTimestamp,
            voterInformation.getTenantId(), voterInformation.getElectionEventId(), voterInformation.getVotingCardId(),
            voterInformation.getBallotId(), voterInformation.getCredentialId(),
            voterInformation.getVerificationCardId(), voterInformation.getBallotBoxId(),
            voterInformation.getVerificationCardSetId(), voterInformation.getVotingCardSetId());

        AuthenticationToken authenticationToken = new AuthenticationToken(voterInformation, base64AuthenticationTokenId,
            currentTimestamp, Base64.getEncoder().encodeToString(tokenSignature));

        return authenticationToken;
    }

    private ConfirmationInformation buildAndSaveEntities(AuthenticationToken authenticationToken) throws Exception {

        String testBallotBoxJson = createBallotBoxJsonObjectString();

        String testElectionInformationContentJsonString = createElectionInformationContentJson();

        BallotBoxInformation ballotBoxInformation = new BallotBoxInformation();
        ballotBoxInformation.setBallotBoxId(TEST_BALLOT_BOX_ID);
        ballotBoxInformation.setJson(testBallotBoxJson);
        ballotBoxInformation.setSignature(TEST_SIGNATURE);
        ballotBoxInformation.setTenantId(TEST_TENANT_ID);
        ballotBoxInformation.setElectionEventId(TEST_ELECTION_EVENT_ID);

        ElectionInformationContent electionInformationContent = new ElectionInformationContent();
        electionInformationContent.setJson(testElectionInformationContentJsonString);
        electionInformationContent.setTenantId(TEST_TENANT_ID);
        electionInformationContent.setElectionEventId(TEST_ELECTION_EVENT_ID);

        String confirmationMessageSignature = StringUtils.join(testConfirmationKey, authenticationToken.getSignature(),
            TEST_VOTING_CARD_ID, TEST_ELECTION_EVENT_ID);

        byte[] confirmationMessageSignatureBytes =
            CryptoUtils.sign(keyPairForSigning.getPrivate(), confirmationMessageSignature);

        String b64ConfirmationMessageSignature = Base64.getEncoder().encodeToString(confirmationMessageSignatureBytes);

        ConfirmationMessage testConfirmationMessage = new ConfirmationMessage();
        testConfirmationMessage.setConfirmationKey(testConfirmationKey);
        testConfirmationMessage.setSignature(b64ConfirmationMessageSignature);

        ConfirmationInformation confirmationInformation = new ConfirmationInformation();
        confirmationInformation.setCredentialId(TEST_CREDENTIAL_ID);
        confirmationInformation.setCertificate(testFinalCertificate);
        confirmationInformation.setConfirmationMessage(testConfirmationMessage);

        userTransaction.begin();
        entityManager.persist(ballotBoxInformation);
        entityManager.persist(electionInformationContent);
        userTransaction.commit();

        return confirmationInformation;
    }

    private String createBallotBoxJsonObjectString() {

        JsonObjectBuilder encryptionParametersJsonBuilder = Json.createObjectBuilder();
        encryptionParametersJsonBuilder.add("p", TEST_P_VALUE);
        encryptionParametersJsonBuilder.add("q", TEST_Q_VALUE);
        encryptionParametersJsonBuilder.add("g", TEST_G_VALUE);

        JsonObject encryptionParametersObject = encryptionParametersJsonBuilder.build();

        JsonObjectBuilder ballotBoxJsonBuilder = Json.createObjectBuilder();
        ballotBoxJsonBuilder.add("id", TEST_ID);
        ballotBoxJsonBuilder.add("encryptionParameters", encryptionParametersObject);
        ballotBoxJsonBuilder.add("eeid", TEST_ELECTION_EVENT_ID);
        ballotBoxJsonBuilder.add("startDate", "2016-01-01T09:00Z");
        ballotBoxJsonBuilder.add("endDate", "3017-12-12T10:00Z");
        ballotBoxJsonBuilder.add("test", false);
        ballotBoxJsonBuilder.add("gracePeriod", "0");
        ballotBoxJsonBuilder.add("confirmationRequired", true);
        ballotBoxJsonBuilder.add("ballotBoxCert", testRootCACertificateJson);
        ballotBoxJsonBuilder.add("electoralAuthorityId", "9ab42a1ee2574b52993f876ac29981d4");

        return ballotBoxJsonBuilder.build().toString();
    }

    private String createElectionInformationContentJson() {

        JsonObjectBuilder electionInformationContentJsonBuilder = Json.createObjectBuilder();
        electionInformationContentJsonBuilder.add("electionEventId", TEST_ELECTION_EVENT_ID);
        electionInformationContentJsonBuilder.add("electionRootCA", testRootCACertificateJson);
        electionInformationContentJsonBuilder.add("servicesCA", testRootCACertificateJson);
        electionInformationContentJsonBuilder.add("authoritiesCA", testRootCACertificateJson);
        electionInformationContentJsonBuilder.add("credentialsCA", testRootCACertificateJson);
        electionInformationContentJsonBuilder.add("adminBoard", testRootCACertificateJson);

        JsonObjectBuilder electionInformationParamsBuilder = Json.createObjectBuilder();
        electionInformationParamsBuilder.add("numVotesPerVotingCard", "1");
        electionInformationParamsBuilder.add("numVotesPerAuthToken", "1");
        JsonObject electionInformationParams = electionInformationParamsBuilder.build();

        electionInformationContentJsonBuilder.add("electionInformationParams", electionInformationParams);

        return electionInformationContentJsonBuilder.build().toString();
    }
}
