/*
 * 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 java.nio.file.Files.copy;
import static java.nio.file.Files.createDirectories;
import static java.nio.file.Files.newDirectoryStream;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonString;
import javax.json.JsonValue;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import com.scytl.products.oscore.logging.api.domain.Level;
import com.scytl.products.oscore.logging.api.domain.LogContent;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.ov.commons.beans.domain.model.ballotbox.BallotBoxIdImpl;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.path.PathResolver;
import com.scytl.products.ov.commons.util.JsonUtils;
import com.scytl.products.ov.sdm.application.log.SdmSecureLogEvent;
import com.scytl.products.ov.sdm.domain.model.EntityRepository;
import com.scytl.products.ov.sdm.domain.model.administrationauthority.AdministrationAuthorityRepository;
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.ballottext.BallotTextRepository;
import com.scytl.products.ov.sdm.domain.model.electionevent.ElectionEventRepository;
import com.scytl.products.ov.sdm.domain.model.electoralauthority.ElectoralAuthorityRepository;
import com.scytl.products.ov.sdm.domain.model.status.Status;
import com.scytl.products.ov.sdm.domain.model.votingcardset.VotingCardSetRepository;
import com.scytl.products.ov.sdm.domain.service.ConfigConstants;
import com.scytl.products.ov.sdm.infrastructure.JsonConstants;

@Service
public class ExportImportService {
    public static final int MAX_DEPTH = 1;

    public static final String SDM = "sdm_";

    public static final String MANY_CHARS_REGEX = "(.*)";

    public static final String BALLOT_BOX_ID_PLACEHOLDER = "%s";

    public static final String PAYLOAD_REGEX_FORMAT =
        new BallotBoxIdImpl(MANY_CHARS_REGEX, MANY_CHARS_REGEX, BALLOT_BOX_ID_PLACEHOLDER).toString()
            + MANY_CHARS_REGEX;

    private static final Logger LOGGER = LoggerFactory.getLogger(ExportImportService.class);

    private static final CopyOption[] COPY_OPTIONS =
        {StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES };

    @Autowired
    private ElectionEventRepository electionEventRepository;

    @Autowired
    private BallotBoxRepository ballotBoxRepository;

    @Autowired
    private BallotRepository ballotRepository;

    @Autowired
    private BallotTextRepository ballotTextRepository;

    @Autowired
    private VotingCardSetRepository votingCardSetRepository;

    @Autowired
    private ElectoralAuthorityRepository electoralAuthorityRepository;

    @Autowired
    private AdministrationAuthorityRepository administrationAuthorityRepository;

    @Autowired
    private PathResolver pathResolver;

    @Autowired
    @Qualifier(value = "absolutePath")
    private PathResolver absolutePathResolver;

    @Autowired
    private SecureLoggingWriter secureLogger;

    @Autowired
    private HashService hashService;

    public void dumpDatabase(final String eeid) throws IOException, ResourceNotFoundException {
        JsonObjectBuilder dumpJsonBuilder = Json.createObjectBuilder();

        String adminboards = administrationAuthorityRepository.list();
        dumpJsonBuilder.add(JsonConstants.JSON_ATTRIBUTE_NAME_ADMINISTRATION_AUTHORITIES,
            JsonUtils.getJsonObject(adminboards).getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_RESULT));

        String electionEvent = electionEventRepository.find(eeid);

        if (StringUtils.isEmpty(electionEvent) || JsonConstants.JSON_EMPTY_OBJECT.equals(electionEvent)) {
            throw new ResourceNotFoundException("Election Event not found");
        }

        JsonArray electionEvents = Json.createArrayBuilder().add(JsonUtils.getJsonObject(electionEvent)).build();
        dumpJsonBuilder.add(JsonConstants.JSON_ATTRIBUTE_NAME_ELECTION_EVENTS, electionEvents);

        String ballots = ballotRepository.listByElectionEvent(eeid);
        JsonArray ballotsArray =
            JsonUtils.getJsonObject(ballots).getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_RESULT);
        dumpJsonBuilder.add(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOTS, ballotsArray);

        JsonArrayBuilder ballotBoxTextsArrayBuilder = Json.createArrayBuilder();
        for (JsonValue ballotValue : ballotsArray) {
            JsonObject ballotObject = (JsonObject) ballotValue;
            String id = ballotObject.getString(JsonConstants.JSON_ATTRIBUTE_NAME_ID);
            String ballotTexts =
                ballotTextRepository.list(Collections.singletonMap(JsonConstants.JSON_STRUCTURE_NAME_BALLOT_ID, id));
            JsonArray ballotTextsForBallot =
                JsonUtils.getJsonObject(ballotTexts).getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_RESULT);
            for (JsonValue ballotText : ballotTextsForBallot) {
                ballotBoxTextsArrayBuilder.add(ballotText);
            }
        }
        dumpJsonBuilder.add(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOT_TEXTS, ballotBoxTextsArrayBuilder.build());

        String ballotBoxes = ballotBoxRepository.listByElectionEvent(eeid);
        dumpJsonBuilder.add(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOT_BOXES,
            JsonUtils.getJsonObject(ballotBoxes).getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_RESULT));

        String votingCardSets = votingCardSetRepository.listByElectionEvent(eeid);
        dumpJsonBuilder.add(JsonConstants.JSON_ATTRIBUTE_NAME_VOTING_CARD_SETS,
            JsonUtils.getJsonObject(votingCardSets).getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_RESULT));

        String electoralAuthorities = electoralAuthorityRepository.listByElectionEvent(eeid);
        dumpJsonBuilder.add(JsonConstants.JSON_ATTRIBUTE_NAME_ELECTORAL_AUTHORITIES,
            JsonUtils.getJsonObject(electoralAuthorities).getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_RESULT));

        Path dumpPath = getPathOfDumpDatabase();
        String fileHash = "";
        String fileName = dumpPath.getFileName().toString();

        try {
            Files.write(dumpPath, dumpJsonBuilder.build().toString().getBytes(StandardCharsets.UTF_8));
            try {
                fileHash = hashService.getB64FileHash(dumpPath);
            } catch (HashServiceException e) {
                LOGGER.error("Error while hashing dump file {} ", fileName, e);
            }

            LOGGER.info("Database export to dump file has been completed successfully: {}", dumpPath.toString());
            secureLogger.log(Level.INFO,
                new LogContent.LogContentBuilder().electionEvent(eeid)
                    .logEvent(SdmSecureLogEvent.DATABASE_EXPORTED_TO_DUMP_FILE_SUCCESSFULLY)
                    .additionalInfo("file_name", fileName).additionalInfo("file_h", fileHash).createLogInfo());
        } catch (IOException e) {
            String errMsg = "An error occurred writing DB dump to: " + dumpPath.toString();
            LOGGER.error(errMsg, e);
            secureLogger.log(Level.ERROR,
                new LogContent.LogContentBuilder().electionEvent(eeid)
                    .logEvent(SdmSecureLogEvent.DATABASE_EXPORTING_TO_DUMP_FILE_FAILED)
                    .additionalInfo("file_name", fileName).additionalInfo("err_desc", errMsg + ", " + e.getMessage())
                    .createLogInfo());
        }

    }

    public void importDatabase() throws IOException {

        Path dumpPath = getPathOfDumpDatabase();
        if (!Files.exists(dumpPath)) {
            String errMsg = "There is no dump database to import";
            LOGGER.warn(errMsg);
            secureLogger.log(Level.WARN,
                new LogContent.LogContentBuilder().logEvent(SdmSecureLogEvent.DUMP_FILE_NOT_EXISTS)
                    .additionalInfo("file_path", dumpPath.toString()).additionalInfo("err_desc", errMsg)
                    .createLogInfo());
            return;
        }

        String dump;
        try {
            dump = new String(Files.readAllBytes(dumpPath), StandardCharsets.UTF_8);
        } catch (IOException e) {
            secureLogger.log(Level.ERROR,
                new LogContent.LogContentBuilder().logEvent(SdmSecureLogEvent.DUMP_FILE_IMPORTING_TO_DB_FAILED)
                    .additionalInfo("file_name", dumpPath.getFileName().toString())
                    .additionalInfo("error_desc", "Error reading import file from: " + dumpPath).createLogInfo());
            throw new IOException("Error reading import file ", e);
        }

        JsonObject dumpJson = JsonUtils.getJsonObject(dump);

        String b64HashDBImport = "";
        try {
            b64HashDBImport = hashService.getB64Hash(dump.getBytes(StandardCharsets.UTF_8));
        } catch (HashServiceException e) {
            LOGGER.error("Error computing the hash of database import", e);
        }

        JsonArray adminboards = dumpJson.getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_ADMINISTRATION_AUTHORITIES);
        for (JsonValue adminBoard : adminboards) {
            saveOrUpdate(adminBoard, administrationAuthorityRepository);
        }

        JsonArray electionEvents = dumpJson.getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_ELECTION_EVENTS);
        for (JsonValue electionEvent : electionEvents) {
            saveOrUpdate(electionEvent, electionEventRepository);
        }

        JsonArray ballots = dumpJson.getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOTS);
        for (JsonValue ballot : ballots) {
            saveOrUpdate(ballot, ballotRepository);
        }

        JsonArray ballotTexts = dumpJson.getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOT_TEXTS);
        for (JsonValue ballotText : ballotTexts) {
            saveOrUpdate(ballotText, ballotTextRepository);
        }

        JsonArray ballotBoxes = dumpJson.getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOT_BOXES);
        for (JsonValue ballotBox : ballotBoxes) {
            saveOrUpdate(ballotBox, ballotBoxRepository);
        }

        JsonArray votingCardSets = dumpJson.getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_VOTING_CARD_SETS);
        for (JsonValue votingCardSet : votingCardSets) {
            saveOrUpdate(votingCardSet, votingCardSetRepository);
        }

        JsonArray electoralAuthorities = dumpJson.getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_ELECTORAL_AUTHORITIES);
        for (JsonValue electoralAuthority : electoralAuthorities) {
            saveOrUpdate(electoralAuthority, electoralAuthorityRepository);
        }

        secureLogger.log(Level.INFO,
            new LogContent.LogContentBuilder().logEvent(SdmSecureLogEvent.DUMP_FILE_IMPORTED_TO_DB_SUCCESSFULLY)
                .additionalInfo("file_h", b64HashDBImport).createLogInfo());
    }

    public Path getPathOfDumpDatabase() {
        return pathResolver.resolve(ConfigConstants.SDM_DIR_NAME).resolve(ConfigConstants.DBDUMP_FILE_NAME);
    }

    private void saveOrUpdate(final JsonValue entity, final EntityRepository repository) {
        JsonObject entityObject = (JsonObject) entity;
        String id = entityObject.getString(JsonConstants.JSON_ATTRIBUTE_NAME_ID);
        String foundEntityString = repository.find(id);
        JsonObject foundEntityObject = JsonUtils.getJsonObject(foundEntityString);
        if (foundEntityObject.isEmpty()) {

            repository.save(entity.toString());
        } else if (!foundEntityObject.containsKey(JsonConstants.JSON_ATTRIBUTE_NAME_STATUS)
            || !entityObject.containsKey(JsonConstants.JSON_ATTRIBUTE_NAME_STATUS)) {

            repository.update(entity.toString());
        } else {

            try {

                String entityStatus = entityObject.getString(JsonConstants.JSON_ATTRIBUTE_NAME_STATUS);
                Status entityStatusEnumValue = Enum.valueOf(Status.class, entityStatus);
                String foundEntityStatus = foundEntityObject.getString(JsonConstants.JSON_ATTRIBUTE_NAME_STATUS);
                Status foundEntityStatusEnumValue = Enum.valueOf(Status.class, foundEntityStatus);

                if (foundEntityStatusEnumValue.isBefore(entityStatusEnumValue)) {
                    repository.delete(id);
                    repository.save(entity.toString());
                } else {
                    LOGGER.warn("Entity {} can't be updated", id);
                }
            } catch (IllegalArgumentException e) {
				LOGGER.error(
						"Not supported entity status found. You might need a new version of this tool that supports such type.",
						e);
            }
        }
    }

    /**
     * Export Election Event Data only, without voting cards nor customer
     * specific data
     * 
     * @param usbDrive
     *            usb drive
     * @param eeId
     *            election event id
     * @param eeAlias
     *            election event alias
     * @throws IOException
     */
    public void exportElectionEventWithoutElectionInformation(String usbDrive, String eeId, String eeAlias)
            throws IOException {

        Path sdmFolder = pathResolver.resolve(ConfigConstants.SDM_DIR_NAME);
        Path usbSdmFolder = absolutePathResolver.resolve(usbDrive, SDM + eeAlias);

        Path configFolder = sdmFolder.resolve(ConfigConstants.CONFIG_DIR_NAME);
        Path toConfigFolder = usbSdmFolder.resolve(ConfigConstants.CONFIG_DIR_NAME);

        Set<String> files = new HashSet<>();
        files.add(ConfigConstants.SDM_CONFIG_DIR_NAME);
        files.add(ConfigConstants.CONFIG_DIR_NAME_ELECTIONS_CONFIG_JSON);// Copy
                                                                         // elections_config.json
        files.add(eeId);
        files.add(ConfigConstants.CONFIG_DIR_NAME);
        files.add(ConfigConstants.CONFIG_FILE_NAME_ENCRYPTION_PARAMETERS_JSON);// Copy
                                                                               // encryptionParameters.json
        files.add(ConfigConstants.CONFIG_DIR_NAME_OFFLINE);
        files.add(ConfigConstants.CONFIG_FILE_NAME_AUTHORITIESCA_PEM);// Copy
                                                                      // authoritiesca.pem
                                                                      // file
        files.add(ConfigConstants.DBDUMP_FILE_NAME);// Copy db_dump.json file
        files.add(ConfigConstants.CONFIG_FILE_NAME_PLATFORM_ROOT_CA); // platformRootCA.pem file
        Filter<Path> filter = file -> files.contains(file.getFileName().toString());
        copyFolder(sdmFolder, usbSdmFolder, filter, false);

        // Copy all csr folder
        filter = file -> true;
        Path csrFolder = configFolder.resolve(ConfigConstants.CSR_FOLDER);
        Path csrUSBFolder = toConfigFolder.resolve(ConfigConstants.CSR_FOLDER);
        copyFolder(csrFolder, csrUSBFolder, filter, false);

        // Copy ONLINE folder without voterMaterial, voteVerification ,
        // electionInformation and printing
        Set<String> exceptions = new HashSet<>();
        exceptions.add(ConfigConstants.CONFIG_DIR_NAME_VOTERMATERIAL);
        exceptions.add(ConfigConstants.CONFIG_DIR_NAME_VOTERVERIFICATION);
        exceptions.add(ConfigConstants.CONFIG_DIR_NAME_ELECTIONINFORMATION);
        exceptions.add(ConfigConstants.CONFIG_DIR_NAME_PRINTING);
        filter = file -> !exceptions.contains(file.getFileName().toString());

        Path onlineFolder = configFolder.resolve(eeId).resolve(ConfigConstants.CONFIG_DIR_NAME_ONLINE);
        Path onlineUSBFolder = toConfigFolder.resolve(eeId).resolve(ConfigConstants.CONFIG_DIR_NAME_ONLINE);
        copyFolder(onlineFolder, onlineUSBFolder, filter, false);

        Set<String> requiredFiles = new HashSet<>();
        requiredFiles.add(ConfigConstants.CONFIG_FILE_NAME_VOTER_INFORMATION + ConfigConstants.CSV);
        requiredFiles
            .add(ConfigConstants.CONFIG_FILE_NAME_VOTER_INFORMATION + ConfigConstants.CSV + ConfigConstants.SIGN);
        requiredFiles.add(ConfigConstants.CONFIG_FILE_NAME_VERIFICATIONSET_DATA);
        requiredFiles.add(ConfigConstants.CONFIG_FILE_NAME_VERIFICATIONSET_DATA + ConfigConstants.SIGN);
        filter = file -> Files.isDirectory(file) || requiredFiles.contains(file.getFileName().toString());

        // Copy voterInformation.csv
        Path voterMaterialFolder = configFolder.resolve(eeId).resolve(ConfigConstants.CONFIG_DIR_NAME_ONLINE)
            .resolve(ConfigConstants.CONFIG_DIR_NAME_VOTERMATERIAL);
        Path voterMaterialUsbFolder = toConfigFolder.resolve(eeId).resolve(ConfigConstants.CONFIG_DIR_NAME_ONLINE)
            .resolve(ConfigConstants.CONFIG_DIR_NAME_VOTERMATERIAL);
        copyFolder(voterMaterialFolder, voterMaterialUsbFolder, filter, false);

        // Copy verificationCardSetData.json
        Path voteVerificationFolder = configFolder.resolve(eeId).resolve(ConfigConstants.CONFIG_DIR_NAME_ONLINE)
            .resolve(ConfigConstants.CONFIG_DIR_NAME_VOTERVERIFICATION);
        Path voteVerificationUsbFolder = toConfigFolder.resolve(eeId).resolve(ConfigConstants.CONFIG_DIR_NAME_ONLINE)
            .resolve(ConfigConstants.CONFIG_DIR_NAME_VOTERVERIFICATION);
        copyFolder(voteVerificationFolder, voteVerificationUsbFolder, filter, false);

    }

    /**
     * Exports the files of the election information context for a given
     * election event
     * 
     * @param usbDrive
     * @param eeId
     * @param eeAlias
     * @throws IOException
     */
    public void exportElectionEventElectionInformation(String usbDrive, String eeId, String eeAlias)
            throws IOException {
        Path sdmFolder = pathResolver.resolve(ConfigConstants.SDM_DIR_NAME);
        Path usbSdmFolder = absolutePathResolver.resolve(usbDrive, SDM + eeAlias);

        Path configFolder = sdmFolder.resolve(ConfigConstants.CONFIG_DIR_NAME);
        Path toConfigFolder = usbSdmFolder.resolve(ConfigConstants.CONFIG_DIR_NAME);
		Path electionInformationFolder = configFolder.resolve(eeId).resolve(ConfigConstants.CONFIG_DIR_NAME_ONLINE)
				.resolve(ConfigConstants.CONFIG_DIR_NAME_ELECTIONINFORMATION);
		Path electionInformationUsbFolder = toConfigFolder.resolve(eeId).resolve(ConfigConstants.CONFIG_DIR_NAME_ONLINE)
				.resolve(ConfigConstants.CONFIG_DIR_NAME_ELECTIONINFORMATION);
        if (Files.exists(electionInformationFolder)) {
            copyFolder(electionInformationFolder, electionInformationUsbFolder,
                getElectionInformationFilter(electionInformationFolder), true);
        } else {
            LOGGER.warn("No files have been found in the election information folder");
        }
    }

    /**
     * Export Voting Cards only
     * 
     * @param usbDrive
     *            usb drive
     * @param eeId
     *            election event id
     * @param eeAlias
     *            election event alias
     * @throws IOException
     */
    public void exportVotingCards(String usbDrive, String eeId, String eeAlias) throws IOException {

        Path sdmOnlineFolder = pathResolver.resolve(ConfigConstants.SDM_DIR_NAME, ConfigConstants.CONFIG_DIR_NAME,
            eeId, ConfigConstants.CONFIG_DIR_NAME_ONLINE);
        Path usbOnlineFolder = absolutePathResolver.resolve(usbDrive, SDM + eeAlias, ConfigConstants.CONFIG_DIR_NAME,
            eeId, ConfigConstants.CONFIG_DIR_NAME_ONLINE);

        // Copy voterMaterial folder
        Set<String> exceptions = new HashSet<>();
        exceptions.add(ConfigConstants.CONFIG_FILE_NAME_VOTER_INFORMATION + ConfigConstants.CSV);
        exceptions.add(ConfigConstants.CONFIG_FILE_NAME_VOTER_INFORMATION + ConfigConstants.CSV + ConfigConstants.SIGN);
        exceptions.add(ConfigConstants.CONFIG_FILE_NAME_VERIFICATIONSET_DATA);
        exceptions.add(ConfigConstants.CONFIG_FILE_NAME_VERIFICATIONSET_DATA + ConfigConstants.SIGN);
        Filter<Path> filter = file -> !exceptions.contains(file.getFileName().toString());
        Path voterMaterialFolder = sdmOnlineFolder.resolve(ConfigConstants.CONFIG_DIR_NAME_VOTERMATERIAL);
        Path usbVoterMaterialFolder = usbOnlineFolder.resolve(ConfigConstants.CONFIG_DIR_NAME_VOTERMATERIAL);
        copyFolder(voterMaterialFolder, usbVoterMaterialFolder, filter, false);

        // Copy voteVerification folder
        Path voteVerification = sdmOnlineFolder.resolve(ConfigConstants.CONFIG_DIR_NAME_VOTERVERIFICATION);
        Path usbVoteVerificationFolder = usbOnlineFolder.resolve(ConfigConstants.CONFIG_DIR_NAME_VOTERVERIFICATION);
        copyFolder(voteVerification, usbVoteVerificationFolder, filter, false);
    }

    /**
     * Export customer specific data only
     * 
     * @param usbDrive
     *            usb drive
     * @param eeId
     *            election event id
     * @param eeAlias
     *            election event alias
     * @throws IOException
     */
    public void exportCustomerSpecificData(String usbDrive, String eeId, String eeAlias) throws IOException {

        Filter<Path> filter = file -> true;
        Path customerFolder = pathResolver.resolve(ConfigConstants.SDM_DIR_NAME, ConfigConstants.CONFIG_DIR_NAME, eeId,
            ConfigConstants.CONFIG_DIR_NAME_CUSTOMER);
        Path usbCustomerFolder = absolutePathResolver.resolve(usbDrive, SDM + eeAlias, ConfigConstants.CONFIG_DIR_NAME,
            eeId, ConfigConstants.CONFIG_DIR_NAME_CUSTOMER);

        copyFolder(customerFolder, usbCustomerFolder, filter, false);
    }

    /**
     * Import all files from selected exported election event to user/sdm
     * 
     * @param usbElectionPath
     *            path to selected exported election event
     * @throws IOException
     */
    public void importData(String usbElectionPath) throws IOException {
        Filter<Path> filter = file -> true;
        Path usbFolder = absolutePathResolver.resolve(usbElectionPath);
        Path sdmFolder = pathResolver.resolve(ConfigConstants.SDM_DIR_NAME);

        copyFolder(usbFolder, sdmFolder, filter, false);
    }

    private void copyFolder(Path source, Path dest, Filter<Path> filter, boolean isExportingElectionInformationFolders)
            throws IOException {
        if (!Files.exists(source))
            return;

        if (Files.isDirectory(source)) {
            if (!Files.exists(dest)) {
                Files.createDirectories(dest);
                LOGGER.info("Directory created from " + source + " to " + dest);
            }
            Filter<Path> electionInformationFilter = filter;
            try (DirectoryStream<Path> stream = Files.newDirectoryStream(source, electionInformationFilter)) {
                for (Path file : stream) {
                    if (isExportingElectionInformationFolders) {
                    	electionInformationFilter = getElectionInformationFilter(file);
                    }
					copyFolder(file, dest.resolve(file.getFileName()), electionInformationFilter,
							isExportingElectionInformationFolders);
                }
            }
        } else {
            String srcPathString = source.toString();
            String destPathString = dest.toString();
            try {

                copy(source, dest, COPY_OPTIONS);

                String fileHash = "";
                try {
                    fileHash = hashService.getB64FileHash(dest);
                } catch (HashServiceException e) {
                    LOGGER.error("Error while hashing file {} ", destPathString, e);
                }
                LOGGER.info("File copied from " + source + " to " + dest);
                secureLogger.log(Level.INFO,
                    new LogContent.LogContentBuilder().logEvent(SdmSecureLogEvent.FILE_COPIED_SUCCESSFULLY)
                        .additionalInfo("file_s", srcPathString).additionalInfo("file_d", destPathString)
                        .additionalInfo("file_h", fileHash).createLogInfo());
            } catch (IOException e) {
                String errMsg = "Error copying files from " + source + " to " + dest;
                LOGGER.error(errMsg, e);
                secureLogger.log(Level.ERROR,
                    new LogContent.LogContentBuilder().logEvent(SdmSecureLogEvent.FILE_COPYING_FAILED)
                        .additionalInfo("file_s", srcPathString).additionalInfo("file_d", destPathString)
                        .additionalInfo("err_desc", errMsg).createLogInfo());
            }
        }
    }

    private Filter<Path> getElectionInformationFilter(Path source) throws IOException {
        /*
         * The decompressed votes will be present only when the ballot box is
         * decrypted. Once that stage is reached, the decrypted contents must
         * not be exported.
         */
        /*
         * This includes legacy tally scenario.
         */
        if (isFilePresent("decompressedVotes.csv", source)) {
            return file -> false;
        }
        /*
         * The downloaded ballot box containing the raw ballot box information
         * will be present with the rest of the audit files (cleansing outputs,
         * previous to last control components mixing payloads) when the control
         * component payload containing the mixed ballot box is downloaded.
         */
        /*
         * This includes legacy offline cleansing and mixing scenarios.
         */
        if (isFilePresent("downloadedBallotBox.csv", source)) {
            String ballotBoxId = source.getFileName().toString();
            // tenantId-(.*)-ballotBoxId(.*)
            String payloadRegex = String.format(PAYLOAD_REGEX_FORMAT, ballotBoxId);
            String regex =
                "^ballotBox(.*)|^downloadedBallotBox(.*)|^failedVotes(.*)|^successfulVotes(.*)|^" + payloadRegex;
            return file -> file.getFileName().toString().matches(regex);
        }

        /*
         * The default scenario is the pre-electoral scenario, so we want to
         * export all the configuration data.
         */
        String regex = "^ballotBox(.*)|^ballot.json$|^electionInformationContents.json(.*)";
        return file -> Files.isDirectory(file) || file.getFileName().toString().matches(regex);
    }

    /**
     * @param fileName
     * @param source
     * @return
     * @throws IOException
     */
    public boolean isFilePresent(String fileName, Path source) throws IOException {

        return Files.walk(source, MAX_DEPTH).filter(path -> path.getFileName().toString().equals(fileName)).findAny()
            .isPresent();
    }

    /**
     * Exports computed choice codes for given USB drive, election event and
     * election event alias.
     * 
     * @param usbDrive
     *            the USB drive
     * @param electionEventId
     *            the election event identifier
     * @param eeAlias
     *            the election event alias
     * @throws IOException
     *             I/O error occurred.
     */
    public void exportComputedChoiceCodes(String usbDrive, String electionEventId, String eeAlias) throws IOException {
        Path sourceFolder = pathResolver.resolve(ConfigConstants.SDM_DIR_NAME, ConfigConstants.CONFIG_DIR_NAME,
            electionEventId, ConfigConstants.CONFIG_DIR_NAME_ONLINE, ConfigConstants.CONFIG_DIR_NAME_VOTERVERIFICATION);
        Path destinationFolder = absolutePathResolver.resolve(usbDrive, SDM + eeAlias, ConfigConstants.CONFIG_DIR_NAME,
            electionEventId, ConfigConstants.CONFIG_DIR_NAME_ONLINE, ConfigConstants.CONFIG_DIR_NAME_VOTERVERIFICATION);

        String json = votingCardSetRepository.listByElectionEvent(electionEventId);
        JsonObject object = JsonUtils.getJsonObject(json);
        JsonArray array = object.getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_RESULT);
        for (JsonValue value : array) {
            JsonObject votingCardSet = (JsonObject) value;
            JsonString attribute = votingCardSet.getJsonString(JsonConstants.JSON_ATTRIBUTE_NAME_STATUS);
            Status status = Status.valueOf(attribute.getString());
            if (status == Status.COMPUTED || status.isBefore(Status.COMPUTED)) {
                // computation results have not been downloaded yet.
                continue;
            }
            attribute = votingCardSet.getJsonString(JsonConstants.JSON_ATTRIBUTE_NAME_VERIFICATIONCARDSETID);
            String verificationCardSetId = attribute.getString();
            Path verificationCardSetFolder = sourceFolder.resolve(verificationCardSetId);
            Filter<Path> filter = ExportImportService::isNodeContributions;
            try (DirectoryStream<Path> files = newDirectoryStream(verificationCardSetFolder, filter)) {
                for (Path source : files) {
                    Path path = sourceFolder.relativize(source);
                    Path destination = destinationFolder.resolve(path);
                    createDirectories(destination.getParent());
                    copy(source, destination, COPY_OPTIONS);
                }
            }
        }
    }
    
    private static boolean isNodeContributions(Path file) {
        String name = file.getFileName().toString();
        return name.startsWith(ConfigConstants.CONFIG_FILE_NAME_NODE_CONTRIBUTIONS)
                && name.endsWith(ConfigConstants.JSON);
    }

    /**
     * Exports pre-computed choice codes for given USB drive, election event and
     * election event alias.
     * 
     * @param usbDrive
     *            the USB drive
     * @param electionEventId
     *            the election event identifier
     * @param eeAlias
     *            the election event alias
     * @throws IOException
     *             I/O error occurred.
     */
    public void exportPreComputedChoiceCodes(String usbDrive, String electionEventId, String eeAlias)
            throws IOException {
        Path sourceFolder = pathResolver.resolve(ConfigConstants.SDM_DIR_NAME, ConfigConstants.CONFIG_DIR_NAME,
            electionEventId, ConfigConstants.CONFIG_DIR_NAME_ONLINE, ConfigConstants.CONFIG_DIR_NAME_VOTERVERIFICATION);
        Path destinationFolder = absolutePathResolver.resolve(usbDrive, SDM + eeAlias, ConfigConstants.CONFIG_DIR_NAME,
            electionEventId, ConfigConstants.CONFIG_DIR_NAME_ONLINE, ConfigConstants.CONFIG_DIR_NAME_VOTERVERIFICATION);

        String json = votingCardSetRepository.listByElectionEvent(electionEventId);
        JsonObject object = JsonUtils.getJsonObject(json);
        JsonArray array = object.getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_RESULT);
        for (JsonValue value : array) {
            JsonObject votingCardSet = (JsonObject) value;
            JsonString attribute = votingCardSet.getJsonString(JsonConstants.JSON_ATTRIBUTE_NAME_STATUS);
            Status status = Status.valueOf(attribute.getString());
            if (status.isBefore(Status.PRECOMPUTED)) {
                // pre-computation has not been done yet.
                continue;
            }
            attribute = votingCardSet.getJsonString(JsonConstants.JSON_ATTRIBUTE_NAME_VERIFICATIONCARDSETID);
            String verificationCardSetId = attribute.getString();
            Path verificationCardSetFolder = sourceFolder.resolve(verificationCardSetId);
            Filter<Path> filter = ExportImportService::isChoiceCodeGenerationPayload;
            try (DirectoryStream<Path> files = newDirectoryStream(verificationCardSetFolder, filter)) {
                for (Path source : files) {
                    Path path = sourceFolder.relativize(source);
                    Path destination = destinationFolder.resolve(path);
                    createDirectories(destination.getParent());
                    copy(source, destination, COPY_OPTIONS);
                }
            }
        }
    }
    
    private static boolean isChoiceCodeGenerationPayload(Path file) {
        String name = file.getFileName().toString();
        return name.startsWith(ConfigConstants.CONFIG_FILE_NAME_PREFIX_CHOICE_CODE_GENERATION_REQUEST_PAYLOAD)
                && name.endsWith(ConfigConstants.CONFIG_FILE_NAME_SUFFIX_CHOICE_CODE_GENERATION_REQUEST_PAYLOAD);
    }

    /**
     * Exports ballot boxes for given USB drive, election event and election
     * event alias.
     * 
     * @param usbDrive
     *            the USB drive
     * @param electionEventId
     *            the election event identifier
     * @param eeAlias
     *            the election event alias
     * @throws IOException
     *             I/O error occurred.
     */
    public void exportBallotBoxes(String usbDrive, String electionEventId, String eeAlias) throws IOException {
        Path sourceFolder = pathResolver.resolve(ConfigConstants.SDM_DIR_NAME, ConfigConstants.CONFIG_DIR_NAME,
            electionEventId, ConfigConstants.CONFIG_DIR_NAME_ONLINE,
            ConfigConstants.CONFIG_DIR_NAME_ELECTIONINFORMATION, ConfigConstants.CONFIG_DIR_NAME_BALLOTS);
        Path destinationFolder = absolutePathResolver.resolve(usbDrive, SDM + eeAlias, ConfigConstants.CONFIG_DIR_NAME,
            electionEventId, ConfigConstants.CONFIG_DIR_NAME_ONLINE,
            ConfigConstants.CONFIG_DIR_NAME_ELECTIONINFORMATION, ConfigConstants.CONFIG_DIR_NAME_BALLOTS);
        copyFolder(sourceFolder, destinationFolder, p -> true, false);
    }
}
