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

import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Scanner;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.cryptolib.elgamal.bean.ElGamalKeyPair;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.ov.commons.path.PathResolver;
import com.scytl.products.ov.commons.sign.MetadataFileSigner;
import com.scytl.products.ov.commons.verify.MetadataFileVerifier;
import com.scytl.products.ov.mixnet.webapp.mvc.MixDecValController;
import com.scytl.products.ov.mixnet.webapp.mvc.MixDecValResult;
import com.scytl.products.ov.sdm.CryptoTestData;
import com.scytl.products.ov.sdm.SDMTestData;
import com.scytl.products.ov.sdm.application.service.IdleStatusService;
import com.scytl.products.ov.sdm.application.service.MixDecValService;
import com.scytl.products.ov.sdm.application.service.PlatformRootCAService;
import com.scytl.products.ov.sdm.domain.model.ballot.BallotRepository;
import com.scytl.products.ov.sdm.domain.model.ballotbox.BallotBoxRepository;
import com.scytl.products.ov.sdm.domain.model.decryption.ValidationJobStatus;
import com.scytl.products.ov.sdm.domain.model.electionevent.ElectionEventRepository;
import com.scytl.products.ov.sdm.domain.service.ConfigConstants;
import com.scytl.products.ov.sdm.domain.service.ProgressManagerService;
import com.scytl.products.ov.sdm.infrastructure.JsonConstants;
import com.scytl.products.ov.sdm.infrastructure.service.ConfigurationEntityStatusService;
import com.scytl.products.ov.sdm.infrastructure.service.impl.ConfigurationEntityStatusServiceImpl;
import com.scytl.products.ov.utils.ConfigObjectMapper;

/**
 * Configuration class for the MixDecValResource integration test
 */
@Configuration
class BackendTestConfig {

    @Autowired
    private ElGamalKeyPair electoralAuthorityKeyPair;

    @Autowired
    private PathResolver pathResolver;

    @Autowired
    private MixDecValController mixDecValController;

    @Bean
    MixDecValService mixDecValService(BallotRepository ballotRepository, BallotBoxRepository ballotBoxRepository,
            ElectionEventRepository electionEventRepository, PathResolver pathResolver,
            ConfigurationEntityStatusService configurationEntityStatusService, MetadataFileSigner metadataFileSigner,
            PlatformRootCAService platformRootCAService, SecureLoggingWriter secureLogger) {
        // Create mix-dec-val service and mock its web app call.
        MixDecValService mixDecValService = new MixDecValService(ballotRepository, ballotBoxRepository,
            electionEventRepository, pathResolver, configurationEntityStatusService, metadataFileSigner,
            platformRootCAService, secureLogger) {
            // Override the original REST call with a mocked invocation of the
            // mix-dec-val controller.
            @Override
            public MixDecValResult requestMixDecValResult(String mixdecvalRequestBody) {
                // Create a controller and mock it.
                MockMvc mockMvc = MockMvcBuilders.standaloneSetup(mixDecValController).build();

                try {
                    // Execute the request.
                    String jsonResponse =
                        mockMvc.perform(MockMvcRequestBuilders.post("/execute").contentType(MediaType.APPLICATION_JSON)
                            .content(mixdecvalRequestBody)).andReturn().getResponse().getContentAsString();

                    return new ConfigObjectMapper().fromJSONToJava(jsonResponse, MixDecValResult.class);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        };

        return mixDecValService;
    }

    @Bean
    IdleStatusService idleStatusService() {
        return new IdleStatusService();
    }

    @Bean
    ProgressManagerService<ValidationJobStatus> progressManagerService() {
        return mock(ProgressManagerService.class);
    }

    @Bean
    MixDecValResource mixDecValResource() {
        return new MixDecValResource();
    }

    @Bean
    BallotRepository ballotRepository() {
        try (Scanner scanner = new Scanner(getClass().getResourceAsStream("/input/ballot.json"))) {
            String ballotJson = scanner.useDelimiter("\\A").next();

            BallotRepository ballotRepository = mock(BallotRepository.class);
            when(ballotRepository.find(anyString())).thenReturn(ballotJson);

            return ballotRepository;
        }
    }

    @Bean
    BallotBoxRepository ballotBoxRepository() throws IOException, GeneralCryptoLibException {
        // Create a ballot box JSON data source and store it.
        String ballotBoxJson = prepareBallotBoxData();

        BallotBoxRepository ballotBoxRepository = mock(BallotBoxRepository.class);
        // Return the ballot box JSON on request.
        when(ballotBoxRepository.find(anyString())).thenReturn(ballotBoxJson);
        // Return the ballot box as the results of a search.
        when(ballotBoxRepository.list(any()))
            .thenReturn("{\"" + JsonConstants.JSON_ATTRIBUTE_NAME_RESULT + "\":[" + ballotBoxJson + "]}");

        return ballotBoxRepository;
    }

    @Bean
    ElectionEventRepository electionEventRepository() throws IOException, GeneralCryptoLibException {
        String electionEventJson = prepareElectionEventData();

        ElectionEventRepository electionEventRepository = mock(ElectionEventRepository.class);
        when(electionEventRepository.find(anyString())).thenReturn(electionEventJson);

        return electionEventRepository;
    }

    @Bean
    ConfigurationEntityStatusService configurationEntityStatusService() {
        return new ConfigurationEntityStatusServiceImpl();
    }

    @Bean
    MetadataFileSigner metadataFileSigner(AsymmetricServiceAPI asymmetricService) throws GeneralCryptoLibException {
        return new MetadataFileSigner(asymmetricService);
    }

    @Bean
    MetadataFileVerifier metadataFileVerifier(AsymmetricServiceAPI asymmetricService) throws GeneralCryptoLibException {
        return new MetadataFileVerifier(asymmetricService);
    }

    @Bean
    AsymmetricServiceAPI asymmetricService() throws GeneralCryptoLibException {
        return new AsymmetricService();
    }

    @Bean
    PlatformRootCAService platformRootCAService(PathResolver pathResolver) throws IOException, GeneralCryptoLibException {
        return new PlatformRootCAService(pathResolver);
    }

    /**
     * Build the ballot box data and store it in its expected location.
     *
     * @return the ballot box data as a JSON string
     */
    private String prepareBallotBoxData() throws GeneralCryptoLibException, IOException {
        Path ballotBoxPath = getBallotBoxPath();
        // Get the ballot box JSON.
        String ballotBoxJson = SDMTestData.generateBallotBoxJson(SDMTestData.BALLOT_BOX_ID, SDMTestData.BALLOT_ID,
            SDMTestData.ELECTORAL_AUTHORITY_ID, electoralAuthorityKeyPair.getPublicKeys(),
            CryptoTestData.getZpSubgroup());
        // Ensure the expected location exists.
        Files.createDirectories(ballotBoxPath);
        // Write the ballot box JSON to its expected location.
        Files.write(ballotBoxPath.resolve("ballotBox.json"), ballotBoxJson.getBytes(Charset.defaultCharset()));

        return ballotBoxJson;
    }

    /**
     * Build the election event data and store it in its expected location.
     *
     * @return the election event data as a JSON string
     */
    private String prepareElectionEventData() throws IOException {
        // The election event JSON, not being part of the runtime config files,
        // is stored in the root of the temporary folder.
        Path electionEventPath = pathResolver.resolve(".");
        // Get the election event JSON.
        String ballotBoxJson = SDMTestData.generateElectionEventJson();
        // Ensure the expected location exists.
        Files.createDirectories(electionEventPath);
        // Write the ballot box JSON to its expected location.
        Files.write(electionEventPath.resolve("electionEvent.json"), ballotBoxJson.getBytes(Charset.defaultCharset()));

        return ballotBoxJson;
    }

    /**
     * @return the path the ballot box data is in.
     */
    public Path getBallotBoxPath() {
        return pathResolver.resolve(ConfigConstants.CONFIG_FILES_BASE_DIR, SDMTestData.ELECTION_EVENT_ID,
            ConfigConstants.CONFIG_DIR_NAME_ONLINE, ConfigConstants.CONFIG_DIR_NAME_ELECTIONINFORMATION,
            ConfigConstants.CONFIG_DIR_NAME_BALLOTS, SDMTestData.BALLOT_ID, ConfigConstants.CONFIG_DIR_NAME_BALLOTBOXES,
            SDMTestData.BALLOT_BOX_ID);
    }
}
