/*
 * 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.infrastructure.preconfiguration;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.orientechnologies.orient.core.storage.ORecordDuplicatedException;
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.infrastructure.exception.OvCommonsInfrastructureException;
import com.scytl.products.ov.commons.infrastructure.remote.client.NodeIdentifier;
import com.scytl.products.ov.commons.infrastructure.remote.client.RestClientConnectionManager;
import com.scytl.products.ov.commons.infrastructure.remote.client.RetrofitConsumer;
import com.scytl.products.ov.commons.infrastructure.remote.client.RetrofitException;
import com.scytl.products.ov.sdm.application.log.SdmSecureLogEvent;
import com.scytl.products.ov.sdm.application.service.HashService;
import com.scytl.products.ov.sdm.application.service.HashServiceException;
import com.scytl.products.ov.sdm.application.service.KeyStoreService;
import com.scytl.products.ov.sdm.infrastructure.clients.AdapterClient;
import com.scytl.products.ov.sdm.infrastructure.clients.AdminPortalClient;
import com.scytl.products.ov.sdm.infrastructure.exception.PreconfigurationRepositoryException;

import okhttp3.ResponseBody;
import retrofit2.Response;

import java.nio.file.Path;
import org.apache.commons.io.output.FileWriterWithEncoding;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;

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 java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.PrivateKey;
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import com.scytl.products.ov.commons.util.JsonUtils;
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.preconfiguration.PreconfigurationRepository;
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.infrastructure.JsonConstants;

/**
 * Implementation of operations with election event.
 */
@Repository
public class PreconfigurationRepositoryImpl implements PreconfigurationRepository {

    private static final String EMPTY_SIGNATURE = "";

	private static final String INPUT_DATE_FORMAT = "dd/MM/yyyy HH:mm";

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

	private static final String ERROR_SAVING_DUPLICATED = "Error saving entity: {0}. It is duplicated.";

	private static final String ERROR_DOWNLOADING_DATA = "Error downloading data from administrator portal. Status code: {0}.";

    @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 SecureLoggingWriter secureLogger;

    @Autowired
    private KeyStoreService keystoreService;

    @Value("${tenantID}")
    private String tenantId;

    @Value("${AP_URL}")
    private String adminPortalBaseURL;

    @Value("${adminPortal.enabled:true}")
    private boolean isAdminPortalEnabled;

    @Value("${adapter.enabled:false}")
    private boolean isAdapterEnabled;

    @Value("${adapter.url:}")
    private String adapterUrl;

    @Value("${confirmation.required:true}")
    private String confirmationRequired;

    @Autowired
    private ObjectReader jsonReader;

    @Autowired
    private HashService hashService;

    /**
     * @see com.scytl.products.ov.sdm.domain.model.preconfiguration.PreconfigurationRepository#readFromFileAndSave(java.lang.String)
     */
    @Override
    public String readFromFileAndSave(final String filename) throws IOException {
        // result
        JsonObjectBuilder jsonBuilderResult = Json.createObjectBuilder();

        // load json from file
        try (InputStream is = Files.newInputStream(Paths.get(filename))) {

            final JsonNode rootNode = jsonReader.readTree(is);

            // save election events
            JsonNode enrichedElectionEvents = enrichElectionEvents(rootNode);
            saveElectionEvents(jsonBuilderResult, enrichedElectionEvents);

            // save ballots
            saveBallots(jsonBuilderResult, rootNode);

            // save ballot texts
            JsonNode enrichedBallotTexts = enrichBallotTexts(rootNode);
            saveBallotTexts(enrichedBallotTexts);

            // save ballot boxes
            saveBallotBoxes(jsonBuilderResult, rootNode);

            // save voting card sets
            saveVotingCardSets(jsonBuilderResult, rootNode);

            // save electoral authorities
            saveElectoralAuthorities(jsonBuilderResult, rootNode);

            // save admin boards
            saveAdminBoards(jsonBuilderResult, rootNode);

            // because the relations ballot/ballotbox, votingcardset/ballot,
            // electoralauthority/ballotbox we needed to put them
            // in the json
            JsonObject result = jsonBuilderResult.build();
            JsonArray arrayBallots = result.getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOTS);
            if (!arrayBallots.isEmpty()) {
                updateBallots(arrayBallots.getValuesAs(JsonString.class));
            }
            JsonArray arrayVotingCardSets = result.getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_VOTING_CARD_SETS);
            if (!arrayVotingCardSets.isEmpty()) {
                updateVotingCardSets(arrayVotingCardSets.getValuesAs(JsonString.class));
            }
            JsonArray arrayElectoralAuthorities =
                result.getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_ELECTORAL_AUTHORITIES);
            if (!arrayElectoralAuthorities.isEmpty()) {
                updateElectoralAuthorities(arrayElectoralAuthorities.getValuesAs(JsonString.class));
            }
            JsonArray arrayBallotBoxes = result.getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOT_BOXES);
            if (!arrayBallotBoxes.isEmpty()) {
                updateBallotBoxes(arrayBallotBoxes.getValuesAs(JsonString.class));
            }
            return result.toString();
        }
    }

    // save admin boards
    private void saveAdminBoards(final JsonObjectBuilder jsonBuilderResult, final JsonNode rootNode) {
        JsonArrayBuilder jsonArraySaved =
            saveFromTree(rootNode.path(JsonConstants.JSON_ATTRIBUTE_NAME_ADMINISTRATION_AUTHORITIES),
                administrationAuthorityRepository);
        jsonBuilderResult.add(JsonConstants.JSON_ATTRIBUTE_NAME_ADMINISTRATION_AUTHORITIES, jsonArraySaved);
    }

    // save electoral authorities
    private void saveElectoralAuthorities(final JsonObjectBuilder jsonBuilderResult, final JsonNode rootNode) {
        JsonArrayBuilder jsonArraySaved = saveFromTree(
            rootNode.path(JsonConstants.JSON_ATTRIBUTE_NAME_ELECTORAL_AUTHORITIES), electoralAuthorityRepository);
        jsonBuilderResult.add(JsonConstants.JSON_ATTRIBUTE_NAME_ELECTORAL_AUTHORITIES, jsonArraySaved);
    }

    // save voting card sets
    private void saveVotingCardSets(final JsonObjectBuilder jsonBuilderResult, final JsonNode rootNode) {
        JsonArrayBuilder jsonArraySaved =
            saveFromTree(rootNode.path(JsonConstants.JSON_ATTRIBUTE_NAME_VOTING_CARD_SETS), votingCardSetRepository);
        jsonBuilderResult.add(JsonConstants.JSON_ATTRIBUTE_NAME_VOTING_CARD_SETS, jsonArraySaved);
    }

    // save ballot boxes
    private void saveBallotBoxes(final JsonObjectBuilder jsonBuilderResult, final JsonNode rootNode) {
        JsonArrayBuilder jsonArraySaved =
            saveBallotBoxesFromTree(rootNode.path(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOT_BOXES), ballotBoxRepository);
        jsonBuilderResult.add(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOT_BOXES, jsonArraySaved);
    }

    // save ballot boxes
    private void saveBallotTexts(final JsonNode rootNode) {
        saveFromTree(rootNode.path(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOT_TRANSLATIONS), ballotTextRepository);
    }

    // save ballot boxes
    private JsonNode enrichBallotTexts(final JsonNode rootNode) throws IOException {
        JsonArrayBuilder jsonArrayProcessed = Json.createArrayBuilder();
        for (JsonNode node : rootNode.path(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOT_TRANSLATIONS)) {
            try {
                // add id attribute to the ballot text with a concatenation
                // between ballot id and locale
                String json = node.toString();
                JsonObject object = JsonUtils.getJsonObject(json);
                String id =
                    node.path(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOT).path(JsonConstants.JSON_ATTRIBUTE_NAME_ID)
                        .textValue() + node.path(JsonConstants.JSON_ATTRIBUTE_NAME_LOCALE).textValue();
                JsonObject jsonObjectWithUpdatedId =
                    JsonUtils.jsonObjectToBuilder(object).add(JsonConstants.JSON_ATTRIBUTE_NAME_ID, id)
                        .add(JsonConstants.JSON_ATTRIBUTE_NAME_SIGNED_OBJECT, EMPTY_SIGNATURE).build();
                jsonArrayProcessed.add(jsonObjectWithUpdatedId);
            } catch (ORecordDuplicatedException e) {
                // duplicated error
                String entityName = ballotTextRepository.getClass().getName();
                LOGGER.error(MessageFormat.format(ERROR_SAVING_DUPLICATED, entityName, e));
            }
        }

        String json = Json.createObjectBuilder()
            .add(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOT_TRANSLATIONS, jsonArrayProcessed).build().toString();

        return jsonReader.readTree(json);
    }

    // save election events
    private void saveElectionEvents(JsonObjectBuilder jsonBuilderResult, JsonNode rootNode) {
        JsonArrayBuilder jsonArraySaved =
            saveFromTree(rootNode.path(JsonConstants.JSON_ATTRIBUTE_NAME_ELECTION_EVENTS), electionEventRepository);
        jsonBuilderResult.add(JsonConstants.JSON_ATTRIBUTE_NAME_ELECTION_EVENTS, jsonArraySaved);
    }

    // save election events
    private JsonNode enrichElectionEvents(final JsonNode rootNode) throws IOException {
        JsonArrayBuilder jsonArrayProcessed = Json.createArrayBuilder();
        for (JsonNode electionEventNode : rootNode.path(JsonConstants.JSON_ATTRIBUTE_NAME_ELECTION_EVENTS)) {
            String electionEventId = electionEventNode.get(JsonConstants.JSON_ATTRIBUTE_NAME_ID).textValue();

            for (JsonNode settingNode : rootNode.path(JsonConstants.JSON_ATTRIBUTE_NAME_SETTINGS)) {
                // add settings and status
                if (electionEventId.equals(settingNode.get(JsonConstants.JSON_ATTRIBUTE_NAME_ELECTION_EVENT)
                    .get(JsonConstants.JSON_ATTRIBUTE_NAME_ID).asText())) {
                    JsonObject electionEvent = JsonUtils.getJsonObject(electionEventNode.toString());
                    JsonObject setting = JsonUtils.getJsonObject(settingNode.toString());
                    JsonObject electionEventWithSettings = JsonUtils.jsonObjectToBuilder(electionEvent)
                        .add(JsonConstants.JSON_ATTRIBUTE_NAME_SETTINGS, setting).build();
                    jsonArrayProcessed.add(electionEventWithSettings);
                }
            }
        }

        String json = Json.createObjectBuilder()
            .add(JsonConstants.JSON_ATTRIBUTE_NAME_ELECTION_EVENTS, jsonArrayProcessed).build().toString();

        return jsonReader.readTree(json);
    }

    // save ballot
    private void saveBallots(final JsonObjectBuilder jsonBuilderResult, final JsonNode rootNode) {
        JsonArrayBuilder jsonArraySaved =
            saveFromTree(rootNode.path(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOTS), ballotRepository);
        jsonBuilderResult.add(JsonConstants.JSON_ATTRIBUTE_NAME_BALLOTS, jsonArraySaved);
    }

    // convert date to iso instance format
    private String convertDate(final String dateInString) {
        DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern(INPUT_DATE_FORMAT);
        ZonedDateTime ldt = ZonedDateTime.parse(dateInString, inputFormatter.withZone(ZoneOffset.UTC));
        return ldt.toInstant().toString();
    }

    // Save from a set of entities taking into account status
    private JsonArrayBuilder saveFromTree(final JsonNode path, final EntityRepository repository) {
        JsonArrayBuilder jsonArraySaved = Json.createArrayBuilder();
        for (JsonNode node : path) {
            try {
                // update dates to correct format in iso instant
                updateDatesToIsoFormat(node);

                // add status attribute to the object with the current status as
                // value
                String json = node.toString();
                JsonObject object = JsonUtils.getJsonObject(json);
                JsonObject jsonObjectWithStatus = JsonUtils.jsonObjectToBuilder(object)
                    .add(JsonConstants.JSON_ATTRIBUTE_NAME_DETAILS,
                        LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm:ss")))
                    .add(JsonConstants.JSON_ATTRIBUTE_NAME_SYNCHRONIZED, Boolean.TRUE.toString()).build();
                Optional<String> optionalId = saveOrUpdate(jsonObjectWithStatus, repository);
                if (optionalId.isPresent()) {
                    jsonArraySaved.add(optionalId.get());
                }
            } catch (ORecordDuplicatedException e) {
                // duplicated error
                String entityName = repository.getClass().getName();
                LOGGER.error(MessageFormat.format(ERROR_SAVING_DUPLICATED, entityName, e));
            }
        }
        return jsonArraySaved;
    }

    // Method for adding the property confirmationRequired to
    // the ballot boxes depending on the SDM
    // configuration This property allows to configure the election in
    // one(confirmationRequired=false) or two phases.
    // The ideal case is to add this property on the AP side

    private JsonArrayBuilder saveBallotBoxesFromTree(JsonNode path, EntityRepository repository) {
        JsonArrayBuilder jsonArraySaved = Json.createArrayBuilder();
        for (JsonNode node : path) {
            try {
                // update dates to correct format in iso instant
                updateDatesToIsoFormat(node);

                // add status attribute to the object with the current status as
                // value
                String json = node.toString();
                JsonObject object = JsonUtils.getJsonObject(json);
                JsonObject jsonObjectWithStatus = JsonUtils.jsonObjectToBuilder(object)
                    .add(JsonConstants.JSON_ATTRIBUTE_NAME_DETAILS,
                        LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm:ss")))
                    .add(JsonConstants.JSON_ATTRIBUTE_NAME_SYNCHRONIZED, Boolean.TRUE.toString())
                    .add(JsonConstants.JSON_ATTRIBUTE_NAME_CONFIRMATION_REQUIRED, this.confirmationRequired).build();
                Optional<String> optionalId = saveOrUpdate(jsonObjectWithStatus, repository);
                if (optionalId.isPresent()) {
                    jsonArraySaved.add(optionalId.get());
                }
            } catch (ORecordDuplicatedException e) {
                // duplicated error
                String entityName = repository.getClass().getName();
                LOGGER.error(MessageFormat.format(ERROR_SAVING_DUPLICATED, entityName, e));
            }
        }
        return jsonArraySaved;
    }

    private Optional<String> saveOrUpdate(final JsonObject entityObject, final EntityRepository repository) {
        String id = entityObject.getString(JsonConstants.JSON_ATTRIBUTE_NAME_ID);
        String foundEntityString = repository.find(id);
        JsonObject foundEntityObject = JsonUtils.getJsonObject(foundEntityString);
        Optional<String> resultId = Optional.empty();
        if (foundEntityObject.isEmpty()) {
            repository.save(entityObject.toString());
            resultId = Optional.of(id);
        } else if (!foundEntityObject.containsKey(JsonConstants.JSON_ATTRIBUTE_NAME_STATUS)
            || !entityObject.containsKey(JsonConstants.JSON_ATTRIBUTE_NAME_STATUS)) {
            repository.update(entityObject.toString());
            resultId = Optional.of(id);
        } else {

            try {

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

                if (entityStatusEnumValue.ordinal() > foundEntityStatusEnumValue.ordinal()) {
                    repository.delete(id);
                    repository.save(entityObject.toString());
                    resultId = Optional.of(id);
                } 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);
            }
        }
        return resultId;
    }

    private void updateDatesToIsoFormat(final JsonNode node) {
        // convert dates to expected format
        if (node.has(JsonConstants.JSON_ATTRIBUTE_NAME_DATE_FROM)) {
            LOGGER.debug("Converting date for 'date from'");

            String originalDate = node.path(JsonConstants.JSON_ATTRIBUTE_NAME_DATE_FROM).textValue();
            LOGGER.debug("Original value: " + originalDate);

            String convertedDate = convertDate(originalDate);
            LOGGER.debug("Converted to: " + convertedDate);

            ((ObjectNode) node).put(JsonConstants.JSON_ATTRIBUTE_NAME_DATE_FROM, convertedDate);
        }

        if (node.has(JsonConstants.JSON_ATTRIBUTE_NAME_DATE_TO)) {
            LOGGER.debug("Converting date for 'date to'");

            String originalDate = node.path(JsonConstants.JSON_ATTRIBUTE_NAME_DATE_TO).textValue();
            LOGGER.debug("Original value: " + originalDate);

            String convertedDate = convertDate(originalDate);
            LOGGER.debug("Converted to: " + convertedDate);

            ((ObjectNode) node).put(JsonConstants.JSON_ATTRIBUTE_NAME_DATE_TO, convertedDate);
        }
    }

    private void updateBallots(final List<JsonString> ids) {
        List<String> list = ids.stream().map(JsonString::getString).collect(Collectors.toList());
        ballotRepository.updateRelatedBallotBox(list);
    }

    private void updateVotingCardSets(final List<JsonString> ids) {
        List<String> list = ids.stream().map(JsonString::getString).collect(Collectors.toList());
        votingCardSetRepository.updateRelatedBallot(list);
    }

    private void updateBallotBoxes(final List<JsonString> ids) {
        List<String> list = ids.stream().map(JsonString::getString).collect(Collectors.toList());
        ballotBoxRepository.updateRelatedBallotAlias(list);
    }

    private void updateElectoralAuthorities(final List<JsonString> ids) {
        List<String> list = ids.stream().map(JsonString::getString).collect(Collectors.toList());
        electoralAuthorityRepository.updateRelatedBallotBox(list);
    }

    /**
     * @see com.scytl.products.ov.sdm.domain.model.preconfiguration.PreconfigurationRepository#download(java.lang.String)
     */
    @Override
    public boolean download(final String filename) throws IOException {

        if (isAdapterEnabled) {
            return downloadUsingAdapter(filename);
        } else {
            LOGGER.info("Downloading directly using the Admin Portal");
            return downloadFromAdmin(filename);
        }

    }

    private boolean downloadUsingAdapter(final String fileName) throws IOException {
        boolean result = false;
        LOGGER.info("Getting information using an adapter from " + adapterUrl);

        AdapterClient adapterClient = getAdapterClient(adapterUrl);
        Response<ResponseBody> executeCall;
        ResponseBody body = null;
        String error = "";
        try {
            executeCall = RetrofitConsumer.executeCall(adapterClient.importConfig());

            result = executeCall.isSuccessful();
            body = executeCall.body();
            LOGGER.info("Connected " + result);
            if (result) {
                // json object
                return downloadFromAdmin(fileName);

            } else {

                if (body != null) {
                    error = new String(body.bytes(), StandardCharsets.UTF_8);
                    LOGGER.error(error);
                }
                secureLogger.log(Level.ERROR,
                    new LogContent.LogContentBuilder().logEvent(SdmSecureLogEvent.AP_CONFIG_DATA_DOWNLOADING_FAILED)
                        .additionalInfo("err_desc", "Error downloading data from administrator portal. ")
                        .additionalInfo("r_url", adminPortalBaseURL).createLogInfo());
                throw new IOException(MessageFormat.format(ERROR_DOWNLOADING_DATA, executeCall.code()));
            }
        } catch (RetrofitException e) {
            ResponseBody errorBody = e.getErrorBody();
            if (errorBody != null) {
                error = new String(errorBody.bytes(), StandardCharsets.UTF_8);
                LOGGER.error(error);
            }
            secureLogger.log(Level.ERROR,
                new LogContent.LogContentBuilder().logEvent(SdmSecureLogEvent.AP_CONFIG_DATA_DOWNLOADING_FAILED)
                    .additionalInfo("err_desc",
                        "Error downloading data from administrator portal. " + error + ", status: " + e.getHttpCode())
                    .additionalInfo("r_code", String.valueOf(e.getHttpCode()))
                    .additionalInfo("r_url", adminPortalBaseURL).createLogInfo());
            throw new IOException(MessageFormat.format(ERROR_DOWNLOADING_DATA, e.getHttpCode()), e);
        }
    }

    private boolean downloadFromAdmin(final String filePathName) throws IOException {
        boolean result;

        LOGGER.info("Trying to download from " + adminPortalBaseURL);

        AdminPortalClient client = getAdminPortalClient(adminPortalBaseURL);

        try {
            Response<ResponseBody> call = RetrofitConsumer.executeCall(client.export(tenantId));

            int status = call.code();
            result = status == javax.ws.rs.core.Response.Status.OK.getStatusCode();

            LOGGER.info("Connected " + result);

            String json = new String(call.body().bytes(), StandardCharsets.UTF_8);
            // save to file
            LOGGER.info("Writing to file " + filePathName);
            saveJson(filePathName, json);

            LOGGER.info("Administration portal successfully downloaded and saved into " + filePathName);
            Path filePath = Paths.get(filePathName);
            String fileHash = "";
            try {
                fileHash = hashService.getB64FileHash(filePath);
            } catch (HashServiceException e) {
                LOGGER.error("Error while hashing downloaded file {} ", filePathName, e);
            }

            String fileName = filePath.getFileName().toString();

            secureLogger.log(Level.INFO,
                new LogContent.LogContentBuilder().logEvent(SdmSecureLogEvent.AP_CONFIG_DATA_DOWNLOADED_SUCCESSFULLY)
                    .additionalInfo("file_name", fileName).additionalInfo("file_h", fileHash).createLogInfo());
        } catch (RetrofitException e) {
            String errMsg = MessageFormat.format(ERROR_DOWNLOADING_DATA, e.getHttpCode());

            LOGGER.error(errMsg, e);

            secureLogger.log(Level.ERROR,
                new LogContent.LogContentBuilder().logEvent(SdmSecureLogEvent.AP_CONFIG_DATA_DOWNLOADING_FAILED)
                    .additionalInfo("err_desc", errMsg).additionalInfo("r_code", String.valueOf(e.getHttpCode()))
                    .additionalInfo("r_url", adminPortalBaseURL).createLogInfo());

            throw new IOException(errMsg);
        }

        return result;
    }

    /**
     * Saves json in to a file.
     *
     * @param filename
     *            name of the file to store data.
     * @param json
     *            the json to be stored.
     * @throws IOException
     *             if something fails when json is saving.
     */
    public void saveJson(final String filename, final String json) throws IOException {
        try (FileWriterWithEncoding fw = new FileWriterWithEncoding(filename, StandardCharsets.UTF_8)) {
            fw.write(json);
        }
    }

    /**
     * Get admin portal client.
     *
     * @param uri
     *            - the URI of the web resource.
     * @return a client to admin portal.
     */
    public AdminPortalClient getAdminPortalClient(final String uri) {
        try {
            PrivateKey privateKey = keystoreService.getPrivateKey();
            return RestClientConnectionManager.getInstance()
                .getRestClientWithInterceptorAndJacksonConverter(uri, privateKey, NodeIdentifier.SECURE_DATA_MANAGER)
                .create(AdminPortalClient.class);
        } catch (OvCommonsInfrastructureException e) {
            throw new PreconfigurationRepositoryException("Error trying to get admin portal client.", e);
        }
    }

    /**
     * Get adapterclient.
     *
     * @param uri
     *            - the URI of the web resource.
     * @return a client to admin portal.
     */
    public AdapterClient getAdapterClient(final String uri) {
        try {
            return RestClientConnectionManager.getInstance().getRestClientWithJacksonConverter(uri)
                .create(AdapterClient.class);
        } catch (OvCommonsInfrastructureException e) {
        	throw new PreconfigurationRepositoryException("Error trying to get adapter client.", e);
        }
    }
}