/*
 * 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.channel.cc.orchestrator.ui.ws.rs.config;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.util.Scanner;

import javax.enterprise.inject.Produces;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.eu.ingwar.tools.arquillian.extension.suite.annotations.ArquillianSuiteDeployment;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.Filters;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.WebArchive;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.cryptolib.proofs.service.ProofsService;
import com.scytl.cryptolib.scytl.keystore.service.ScytlKeyStoreService;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.ov.channel.cc.orchestrator.commons.messaging.MessagingServiceProducer;
import com.scytl.products.ov.channel.cc.orchestrator.infrastructure.health.HealthCheckRegistryProducer;
import com.scytl.products.ov.channel.cc.orchestrator.mixdec.infrastructure.remote.ElectionInformationClient;
import com.scytl.products.ov.channel.cc.orchestrator.mixdec.infrastructure.remote.producer.ElectionInformationRemoteClientProducer;
import com.scytl.products.ov.commons.beans.domain.model.ballotbox.BallotBoxIdImpl;
import com.scytl.products.ov.commons.beans.domain.model.vote.VoteSetIdImpl;
import com.scytl.products.ov.commons.dto.mixing.MixingPayloadMockBuilder;
import com.scytl.products.ov.commons.logging.producers.SecureLoggerProducer;
import com.scytl.products.ov.commons.logging.producers.SplunkableLoggerProducer;
import com.scytl.products.ov.commons.logging.producers.TransactionInfoProviderProducer;
import com.scytl.products.ov.commons.messaging.MessagingService;
import com.scytl.products.ov.commons.messaging.MessagingServiceImpl;
import com.scytl.products.ov.commons.ui.ws.rs.exceptions.ApplicationExceptionHandler;
import com.scytl.products.ov.commons.ui.ws.rs.exceptions.DuplicateEntryExceptionHandler;
import com.scytl.products.ov.commons.ui.ws.rs.exceptions.GlobalExceptionHandler;
import com.scytl.products.ov.commons.ui.ws.rs.exceptions.ResourceNotFoundExceptionHandler;
import com.scytl.products.ov.commons.ui.ws.rs.exceptions.RetrofitErrorHandler;
import com.scytl.products.ov.commons.ui.ws.rs.exceptions.ValidationExceptionHandler;
import com.scytl.products.ov.commons.validation.ValidationResult;

import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.mock.Calls;

@ArquillianSuiteDeployment
public class OrchestratorArquillianDeployment {

    @Deployment(testable = false)
    public static WebArchive buildDeployable() {

        WebArchive webArchive = ShrinkWrap.create(WebArchive.class, "or-ws-rest.war");

        return webArchive
            .addPackages(true,
                Filters.exclude(ScytlKeyStoreService.class, AsymmetricService.class, ProofsService.class),
                "com.scytl.cryptolib")
            .addPackages(true, "org.h2").addPackages(true, "com.scytl.products.ov.channel.cc.orchestrator")
            .addPackages(true, "com.scytl.products.ov.commons.logging.service")
            .addPackages(true, "com.scytl.products.ov.commons.tracking")
            .addPackages(true, "com.scytl.products.ov.commons.crypto").addPackages(true, "com.fasterxml.jackson.jaxrs")
            .addPackages(true, "com.scytl.math")
            .addPackages(true,
                Filters.exclude(ResourceNotFoundExceptionHandler.class, GlobalExceptionHandler.class,
                    ValidationExceptionHandler.class, ApplicationExceptionHandler.class,
                    DuplicateEntryExceptionHandler.class, RetrofitErrorHandler.class, BouncyCastleProvider.class,
                    ScytlKeyStoreService.class),
                "com.scytl.products.ov.commons")
            .addClasses(SecureLoggerProducer.class, SecureLoggingWriter.class, TransactionInfoProviderProducer.class,
                SplunkableLoggerProducer.class, org.slf4j.Logger.class)
            .deleteClass(HealthCheckRegistryProducer.class)
            // Remove the default queue client producers, to be replaced with
            // non-SSL counterparts.
            .deleteClass(MessagingServiceProducer.class).deleteClass(ElectionInformationRemoteClientProducer.class)
            .addAsManifestResource("test-persistence.xml", "persistence.xml").addAsResource("log4j.xml")
            .addAsWebInfResource("beans.xml");
    }

    /**
     * Give out messaging service instances that connect to the embedded message
     * broker, which does not use SSL. SSL connectivity has not been made an
     * option in the production code in order to enforce usage of the former.
     *
     * @return a producer for messaging service instances
     */
    @Produces
    public MessagingService messagingService() {
        return new MessagingServiceImpl.Builder().setHostName("localhost").setPort(5672).setVirtualHost("cco")
            .setUsername("admin").setPassword("admin").setSenderPoolSize(Runtime.getRuntime().availableProcessors())
            .build();
    }

    /**
     * A mock of the election information client that provides a fixed set of
     * ballot boxes.
     */
    public static class ElectionInformationClientMock implements ElectionInformationClient {
        @Override
        public Call<String> getBallotBoxInformation(String trackId, String pathBallotBox, String tenantId,
                String electionEventId, String ballotBoxId) {
            String bbInfo;
            try (InputStream stream = getClass().getResourceAsStream("/bb-info.json");
                    Scanner scanner = new Scanner(stream)) {
                bbInfo = scanner.useDelimiter("\\A").next();
            } catch (IOException e) {
                throw new AssertionError(e);
            }
            return Calls.response(bbInfo);
        }

        @Override
        public Call<ValidationResult> validateElectionInDates(String requestId, String pathValidations, String tenantId,
                String electionEventId, String ballotBoxId) {
            return Calls.response(new ValidationResult(true));
        }

        @Override
        public Call<ResponseBody> getEncryptedBallotBox(String requestId, String pathCleansedBallotBoxes,
                String tenantId, String electionEventId, String ballotBoxId) {
            return Calls.response(ResponseBody.create(okhttp3.MediaType.parse("text/plain"), "1\n2".getBytes()));
        }

        @Override
        public Call<ResponseBody> getMixingPayloads(String requestId, String pathCleansedBallotBoxes, String tenantId,
                String electionEventId, String ballotBoxId) {
            // Serialise payloads into the response stream.
            try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    ObjectOutput oo = new ObjectOutputStream(baos)) {
                // Create one chunk per ballot box ID.
                oo.writeObject(new MixingPayloadMockBuilder()
                    .withVoteSetId(new VoteSetIdImpl(new BallotBoxIdImpl(tenantId, electionEventId, ballotBoxId), 0))
                    .withSignature(MixingPayloadMockBuilder.getMockSignature()).build());
                // Create the response stream from the serialised payloads.
                return Calls.response(
                    ResponseBody.create(okhttp3.MediaType.parse("application/octet-stream"), baos.toByteArray()));
            } catch (GeneralCryptoLibException | IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
