/*
 * 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 java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;

import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;

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.sdm.application.exception.DatabaseException;
import com.scytl.products.ov.sdm.application.log.SdmSecureLogEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.scytl.products.ov.commons.path.PathResolver;
import com.scytl.products.ov.commons.util.JsonUtils;
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.electoralauthority.ElectoralAuthorityUploadRepository;
import com.scytl.products.ov.sdm.domain.model.status.Status;
import com.scytl.products.ov.sdm.domain.model.status.SynchronizeStatus;
import com.scytl.products.ov.sdm.domain.service.ConfigConstants;
import com.scytl.products.ov.sdm.infrastructure.JsonConstants;

/**
 * Service which uploads files to voter portal after creating the electoral
 * authorities
 */
@Service
public class ElectoralAuthoritiesUploadService {

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

    private static final String CONSTANT_AUTHENTICATION_CONTEXT_DATA = "authenticationContextData";

    private static final String CONSTANT_ELECTORAL_AUTHORITY = "electoralAuthority";

    private static final String CONSTANT_AUTHENTICATION_VOTER_DATA = "authenticationVoterData";

    private static final String NULL_ELECTION_EVENT_ID = "";

    private static final String FILE_NAME = "file_name";

    private static final String FILE_HASH = "file_h";

    @Autowired
    private ElectoralAuthorityRepository electoralAuthorityRepository;

    @Autowired
    private ElectoralAuthorityUploadRepository electoralAuthorityUploadRepository;

    @Autowired
    private PathResolver pathResolver;

    @Autowired
    private ElectionEventRepository electionEventRepository;

    @Autowired
    private SecureLoggingWriter secureLogger;

    @Autowired
    private HashService hashService;

    /**
     * Uploads the available electoral authorities texts to the voter portal.
     */
    public void uploadSyncronizableElectoralAuthorities(String electionEvent) throws IOException {

        Map<String, Object> params = new HashMap<>();
        addSigned(params);
        addPendingToSynchronize(params);

        if (thereIsAnElectionEventIdAsAParameter(electionEvent)) {
            addElectionEventId(electionEvent, params);
        }

        String documents = electoralAuthorityRepository.list(params);
        JsonArray electoralAuthorities =
            JsonUtils.getJsonObject(documents).getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_RESULT);

        for (int i = 0; i < electoralAuthorities.size(); i++) {

            JsonObject electoralAuthoritiesInArray = electoralAuthorities.getJsonObject(i);
            String electoralAuthorityId = electoralAuthoritiesInArray.getString(JsonConstants.JSON_ATTRIBUTE_NAME_ID);
            String electionEventId =
                electoralAuthoritiesInArray.getJsonObject(JsonConstants.JSON_ATTRIBUTE_NAME_ELECTION_EVENT)
                    .getString(JsonConstants.JSON_ATTRIBUTE_NAME_ID);

            JsonObject eEvent = JsonUtils.getJsonObject(electionEventRepository.find(electionEventId));
            JsonObject adminBoard = eEvent.getJsonObject(JsonConstants.JSON_ATTRIBUTE_NAME_ADMINISTRATION_AUTHORITY);

            String adminBoardId = adminBoard.getString(JsonConstants.JSON_ATTRIBUTE_NAME_ID);

            LOGGER.info("Uploading the signed authentication context configuration");
            boolean authResult = uploadAuthenticationContextData(electionEventId, adminBoardId);

            LOGGER.info("Uploading the signed election information context configuration");
            boolean eiResult = uploadElectionInformationData(electionEventId, adminBoardId);

            LOGGER.info("Uploading the signed electoral authority");
            boolean electoralAuthorityUploadResult =
                uploadElectoralAuthority(electionEventId, electoralAuthorityId, adminBoardId);

            JsonObjectBuilder jsonObjectBuilder = Json.createObjectBuilder();
            if (authResult && eiResult && electoralAuthorityUploadResult) {

                LOGGER.info("Changing the status of the electoral authority");
                jsonObjectBuilder.add(JsonConstants.JSON_ATTRIBUTE_NAME_ID, electoralAuthorityId);
                jsonObjectBuilder.add(JsonConstants.JSON_ATTRIBUTE_NAME_SYNCHRONIZED,
                    SynchronizeStatus.SYNCHRONIZED.getIsSynchronized().toString());
                jsonObjectBuilder.add(JsonConstants.JSON_ATTRIBUTE_NAME_DETAILS,
                    SynchronizeStatus.SYNCHRONIZED.getStatus());
                LOGGER.info("The electoral authority was uploaded successfully");
            } else {
                LOGGER.error("An error occurred while uploading the signed electoral authority");
                jsonObjectBuilder.add(JsonConstants.JSON_ATTRIBUTE_NAME_ID, electoralAuthorityId);
                jsonObjectBuilder.add(JsonConstants.JSON_ATTRIBUTE_NAME_DETAILS, SynchronizeStatus.FAILED.getStatus());

            }

            try {
                electoralAuthorityRepository.update(jsonObjectBuilder.build().toString());
            } catch (DatabaseException ex) {
            	LOGGER.error("An error occurred while updating the signed electoral authority", ex);
                secureLogger.log(Level.ERROR, new LogContent.LogContentBuilder()
                        .logEvent(SdmSecureLogEvent.SYNCHRONIZING_WITH_VP_FAILED)
                        .electionEvent(electionEventId)
                        .additionalInfo("err_desc", ex.getMessage())
                        .createLogInfo());
            }
        }

    }

    private void addElectionEventId(final String electionEvent, final Map<String, Object> params) {
        params.put(JsonConstants.JSON_STRUCTURE_NAME_ELECTION_EVENT_ID, electionEvent);
    }

    private boolean thereIsAnElectionEventIdAsAParameter(final String electionEvent) {
        return !NULL_ELECTION_EVENT_ID.equals(electionEvent);
    }

    private void addPendingToSynchronize(final Map<String, Object> params) {
        params.put(JsonConstants.JSON_ATTRIBUTE_NAME_SYNCHRONIZED,
            SynchronizeStatus.PENDING.getIsSynchronized().toString());
    }

    private void addSigned(final Map<String, Object> params) {
        params.put(JsonConstants.JSON_ATTRIBUTE_NAME_STATUS, Status.SIGNED.name());
    }

    private boolean uploadAuthenticationContextData(String electionEventId, final String adminBoardId)
            throws IOException {

        if (electoralAuthorityUploadRepository.checkEmptyElectionEventDataInAU(electionEventId)) {
            Path authenticationPath = pathResolver.resolve(ConfigConstants.CONFIG_FILES_BASE_DIR, electionEventId,
                ConfigConstants.CONFIG_DIR_NAME_ONLINE, ConfigConstants.CONFIG_DIR_NAME_AUTHENTICATION);

            Path authenticationContextPath = pathResolver.resolve(authenticationPath.toString(),
                ConfigConstants.CONFIG_FILE_NAME_SIGNED_AUTH_CONTEXT_DATA);
            Path authenticationVoterDataPath = pathResolver.resolve(authenticationPath.toString(),
                ConfigConstants.CONFIG_FILE_NAME_SIGNED_AUTH_VOTER_DATA);
            JsonObject authenticationContexData = JsonUtils
                .getJsonObject(new String(Files.readAllBytes(authenticationContextPath), StandardCharsets.UTF_8));
            JsonObject authenticationVoterData = JsonUtils
                .getJsonObject(new String(Files.readAllBytes(authenticationVoterDataPath), StandardCharsets.UTF_8));
            JsonObject jsonInput = Json.createObjectBuilder()
                .add(CONSTANT_AUTHENTICATION_CONTEXT_DATA, authenticationContexData.toString())
                .add(CONSTANT_AUTHENTICATION_VOTER_DATA, authenticationVoterData.toString()).build();

            boolean authResult = electoralAuthorityUploadRepository.uploadAuthenticationContextData(electionEventId,
                adminBoardId, jsonInput);
            String authenticationContextDataHash = getB64Hash(jsonInput.toString().getBytes(StandardCharsets.UTF_8));
            String fileName = authenticationContextPath.getFileName().toString();

            if (authResult) {

                secureLogger.log(Level.INFO,
                    new LogContent.LogContentBuilder().electionEvent(electionEventId)
                        .logEvent(SdmSecureLogEvent.AUTHENTICATION_CONTEXT_DATA_UPLOADED_SUCCESSFULLY)
                        .additionalInfo(FILE_NAME, fileName).additionalInfo(FILE_HASH, authenticationContextDataHash)
                        .createLogInfo());
            } else {

                secureLogger.log(Level.ERROR, new LogContent.LogContentBuilder().electionEvent(electionEventId)
                    .logEvent(SdmSecureLogEvent.AUTHENTICATION_CONTEXT_DATA_UPLOADING_FAILED)
                    .additionalInfo(FILE_NAME, fileName)
                    .additionalInfo("err_desc", "Error while uploading authentication context data").createLogInfo());
            }
            return authResult;
        }

        return true;
    }

    private boolean uploadElectionInformationData(String electionEventId, final String adminBoardId)
            throws IOException {

        if (electoralAuthorityUploadRepository.checkEmptyElectionEventDataInEI(electionEventId)) {
            Path electionInformationPath = pathResolver.resolve(ConfigConstants.CONFIG_FILES_BASE_DIR, electionEventId,
                ConfigConstants.CONFIG_DIR_NAME_ONLINE, ConfigConstants.CONFIG_DIR_NAME_ELECTIONINFORMATION);

            Path electionInformationContextPath = pathResolver.resolve(electionInformationPath.toString(),
                ConfigConstants.CONFIG_FILE_NAME_SIGNED_ELECTION_INFORMATION_CONTENTS);

            byte[] electionInformationContextDataBytes = Files.readAllBytes(electionInformationContextPath);

            JsonObject electionInformationContextData = JsonUtils
                .getJsonObject(new String(electionInformationContextDataBytes, StandardCharsets.UTF_8));

            boolean uploadEIContextDataResult = electoralAuthorityUploadRepository
                .uploadElectionInformationContextData(electionEventId, adminBoardId, electionInformationContextData);

            String eiContextDataHash = getB64Hash(electionInformationContextData.toString().getBytes(StandardCharsets.UTF_8));

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

            if (uploadEIContextDataResult) {

                secureLogger.log(Level.INFO, new LogContent.LogContentBuilder().electionEvent(electionEventId)
                    .logEvent(SdmSecureLogEvent.ELECTION_INFORMATION_CONTEXT_DATA_UPLOADED_SUCCESSFULLY)
                    .additionalInfo(FILE_NAME, fileName).additionalInfo(FILE_HASH, eiContextDataHash).createLogInfo());
            } else {

                secureLogger.log(Level.ERROR,
                    new LogContent.LogContentBuilder().electionEvent(electionEventId)
                        .logEvent(SdmSecureLogEvent.ELECTION_INFORMATION_CONTEXT_DATA_UPLOADING_FAILED)
                        .additionalInfo(FILE_NAME, fileName)
                        .additionalInfo("err_desc", "Error while uploading election information data").createLogInfo());
            }

            return uploadEIContextDataResult;

        }
        return true;
    }

    private boolean uploadElectoralAuthority(String electionEventId, String electoralAuthorityId,
            final String adminBoardId) throws IOException {

        JsonObject electoralAuthority = getSignedElectoralAuthorityJSON(electionEventId, electoralAuthorityId);
        JsonObject jsonInput = prepareSignedElectoralAuthorityToBeUploaded(electoralAuthority);

        boolean uploadEAtoVVResult = uploadElectoralAuthorityToVoteVerificationContext(electoralAuthorityId,
            electionEventId, adminBoardId, jsonInput);

        if (uploadEAtoVVResult) {

            secureLogger.log(Level.INFO,
                new LogContent.LogContentBuilder().objectId(electoralAuthorityId).electionEvent(electionEventId)
                    .logEvent(SdmSecureLogEvent.SIGNED_ELECTORAL_AUTHORITY_UPLOADED_TO_VV_SUCCESSFULLY)
                    .additionalInfo("ea_id", electoralAuthorityId).createLogInfo());
        } else {

            secureLogger.log(Level.ERROR,
                new LogContent.LogContentBuilder().objectId(electoralAuthorityId).electionEvent(electionEventId)
                    .logEvent(SdmSecureLogEvent.SIGNED_ELECTORAL_AUTHORITY_UPLOADING_TO_VV_FAILED)
                    .additionalInfo("ea_id", electoralAuthorityId)
                    .additionalInfo("err_desc", "Error while uploading electoral authority to VV").createLogInfo());
        }

        boolean uploadEAtoEIResult = uploadElectoralAuthorityToElectionInformationContext(electoralAuthorityId,
            electionEventId, adminBoardId, jsonInput);

        if (uploadEAtoEIResult) {

            secureLogger.log(Level.INFO,
                new LogContent.LogContentBuilder().objectId(electoralAuthorityId).electionEvent(electionEventId)
                    .logEvent(SdmSecureLogEvent.SIGNED_ELECTORAL_AUTHORITY_UPLOADED_TO_EI_SUCCESSFULLY)
                    .additionalInfo("ea_id", electoralAuthorityId).createLogInfo());
        } else {

            secureLogger.log(Level.ERROR,
                new LogContent.LogContentBuilder().objectId(electoralAuthorityId).electionEvent(electionEventId)
                    .logEvent(SdmSecureLogEvent.SIGNED_ELECTORAL_AUTHORITY_UPLOADING_TO_EI_FAILED)
                    .additionalInfo("ea_id", electoralAuthorityId)
                    .additionalInfo("err_desc", "Error while uploading electoral authority to EI").createLogInfo());
        }

        return uploadEAtoEIResult && uploadEAtoVVResult;
    }

    private JsonObject prepareSignedElectoralAuthorityToBeUploaded(final JsonObject electoralAuthority) {
        return Json.createObjectBuilder().add(CONSTANT_ELECTORAL_AUTHORITY, electoralAuthority).build();
    }

    private JsonObject getSignedElectoralAuthorityJSON(final String electionEventId, final String electoralAuthorityId)
            throws IOException {
    	
        Path electoralAuthorityDataPath = pathResolver.resolve(ConfigConstants.CONFIG_FILES_BASE_DIR, electionEventId,
            ConfigConstants.CONFIG_DIR_NAME_ONLINE, ConfigConstants.CONFIG_DIR_NAME_ELECTORAL_AUTHORITY,
            electoralAuthorityId, ConfigConstants.CONFIG_FILE_NAME_SIGNED_ELECTORAL_AUTHORITY_JSON);

        return JsonUtils
            .getJsonObject(new String(Files.readAllBytes(electoralAuthorityDataPath), StandardCharsets.UTF_8));
    }

    private boolean uploadElectoralAuthorityToVoteVerificationContext(String electoralAuthorityId,
            String electionEventId, final String adminBoardId, JsonObject jsonInput) throws IOException {

        return electoralAuthorityUploadRepository.uploadElectoralDataInVerificationContext(electionEventId,
            electoralAuthorityId, adminBoardId, jsonInput);
    }

    private boolean uploadElectoralAuthorityToElectionInformationContext(String electoralAuthorityId,
            String electionEventId, final String adminBoardId, JsonObject jsonInput) throws IOException {

        return electoralAuthorityUploadRepository.uploadElectoralDataInElectionInformationContext(electionEventId,
            electoralAuthorityId, adminBoardId, jsonInput);
    }

    private String getB64Hash(byte[] dataToHash) {

        String b64Hash = "";
        try {
            b64Hash = hashService.getB64Hash(dataToHash);
        } catch (HashServiceException e) {
            LOGGER.error("Error computing the hash of JSON data", e);
        }

        return b64Hash;
    }
}
