/*
 * 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.sdm.application.service;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Security;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;

import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.products.ov.commons.path.PathResolver;
import com.scytl.products.ov.sdm.domain.model.electionevent.ElectionEventRepository;
import com.scytl.products.ov.sdm.domain.model.generator.DataGeneratorResponse;
import com.scytl.products.ov.sdm.domain.service.ElectionEventDataGeneratorService;
import com.scytl.products.ov.sdm.infrastructure.service.ConfigurationEntityStatusService;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

/**
 * JUnit for the class {@link ElectionEventService}.
 */
@RunWith(MockitoJUnitRunner.class)
public class ElectionEventServiceTest {

    final String eiUrl = "http://localhost:%s/ag-ws-rest/ei";

    @InjectMocks
    private ElectionEventService electionEventService = new ElectionEventService();

    @Mock
    private ElectionEventDataGeneratorService electionEventDataGeneratorServiceMock;

    @Mock
    private ConfigurationEntityStatusService configurationEntityStatusService;

    @Mock
    private PathResolver pathResolver;

    @Mock
    private KeyStoreService keystoreServiceMock;

    private static int httpPort;

    @Test
    public void createElectionEventGenerateFails() throws IOException {
        DataGeneratorResponse resultElectionEventGeneration = new DataGeneratorResponse();
        resultElectionEventGeneration.setSuccessful(false);
        when(electionEventDataGeneratorServiceMock.generate(anyString())).thenReturn(resultElectionEventGeneration);

        assertFalse(electionEventService.create("").isSuccessful());
    }

    @Test
    public void create() throws IOException {
        DataGeneratorResponse resultElectionEventGeneration = new DataGeneratorResponse();
        resultElectionEventGeneration.setSuccessful(true);
        when(electionEventDataGeneratorServiceMock.generate(anyString())).thenReturn(resultElectionEventGeneration);
        when(configurationEntityStatusService.update(anyString(), anyString(), any(ElectionEventRepository.class)))
            .thenReturn("");

        assertTrue(electionEventService.create("").isSuccessful());
    }

    @Test
    public void download_participation_file_writes_output_file_and_returns_path() throws Exception {

        setupHttpServer();

        ReflectionTestUtils.setField(electionEventService, "tenantId", "100");

        String finalUrl = String.format(eiUrl, httpPort);
        ReflectionTestUtils.setField(electionEventService, "electionInformationBaseURL", finalUrl);
        ReflectionTestUtils.setField(electionEventService, "keyStoreService", keystoreServiceMock);

        // when
        when(pathResolver.resolve(anyString())).thenReturn(Paths.get("target"));

        Security.addProvider(new BouncyCastleProvider());
        AsymmetricServiceAPI signer = new AsymmetricService();
        KeyPair keyPairForSigning = signer.getKeyPairForSigning();
        PrivateKey privateKey = keyPairForSigning.getPrivate();
        when(keystoreServiceMock.getPrivateKey()).thenReturn(privateKey);

        // then
        final Path path = electionEventService.downloadParticipationFile("1");

        assertNotNull(path);

    }

    public void setupHttpServer() {
        try {

            setPorts();

            // setup the socket address
            InetSocketAddress address = new InetSocketAddress(httpPort);

            // initialise the HTTP server
            HttpServer httpServer = HttpServer.create(address, 0);
            httpServer.createContext("/", new MyHttpHandler());
            httpServer.setExecutor(null); // creates a default executor
            httpServer.start();

        } catch (Exception exception) {
            System.out.println("Failed to create HTTP server on port 8080 of localhost");
            exception.printStackTrace();

        }
    }

    public static class MyHttpHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {
            String response = "1234567890\n" + "0987654321\n";
            t.getResponseHeaders().add("Content-Type", "application/octet-stream");
            t.getResponseHeaders().add("Content-Disposition", "attachment; filename=test.csv");
            t.sendResponseHeaders(200, response.length());
            OutputStream os = t.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }

    private static void setPorts() throws IOException {
        httpPort = discoverFreePorts(50000, 60000);
    }

    private static int discoverFreePorts(int from, int to) throws IOException {
        int result = 0;
        ServerSocket tempServer = null;

        for (int i = from; i <= to; i++) {
            try {
                tempServer = new ServerSocket(i);
                result = tempServer.getLocalPort();
                break;

            } catch (IOException ex) {
                continue; // try next port
            }
        }

        if (result == 0) {
            throw new IOException("no free port found");
        }

        tempServer.close();
        return result;
    }
}
