/*
 * 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.ag.ui.ws.rs.operation.monitoring;

import com.scytl.products.ov.commons.infrastructure.health.HealthCheckValidationType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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.commons.ui.ws.rs.ObjectMappers;

import javax.annotation.PostConstruct;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

import com.scytl.products.ov.ag.services.infrastructure.remote.monitoring.HealthMonitoringClient;
import com.scytl.products.ov.ag.ui.ws.rs.RestApplication;
import com.scytl.products.ov.commons.infrastructure.health.HealthCheck;
import com.scytl.products.ov.commons.infrastructure.health.HealthCheckStatus;

/**
 * Web service that provides functionalities to monitoring secure log system.
 */
@Stateless(name = "ag-PlatformHealthResource")
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
@Path(RestApplication.API_OV_MONITORING_VERSION_BASEURI)
public class PlatformHealthResource {

	static final String CHECK_PLATFORM_HEALTH = "/check";

	private Map<String, HealthMonitoringClient> clientMap = new HashMap<>();

	private static final Logger LOG = LoggerFactory.getLogger(PlatformHealthResource.class);

	@Inject @Named(HealthMonitoringClient.AU)
	private HealthMonitoringClient authenticationMonitoringClient;
	@Inject @Named(HealthMonitoringClient.EA)
	private HealthMonitoringClient extendedAuthenticationMonitoringClient;
	@Inject @Named(HealthMonitoringClient.EI)
	private HealthMonitoringClient electionInformationMontoringClient;
	@Inject @Named(HealthMonitoringClient.VM)
	private HealthMonitoringClient voterMaterialMonitoringClient;
	@Inject @Named(HealthMonitoringClient.VV)
	private HealthMonitoringClient voteVerificationMonitoringClient;
	@Inject @Named(HealthMonitoringClient.VW)
	private HealthMonitoringClient votingWorkflowMonitoringClient;
	@Inject @Named(HealthMonitoringClient.CR)
	private HealthMonitoringClient certificateRegistryMonitoringClient;
	@Inject @Named(HealthMonitoringClient.KT)
	private HealthMonitoringClient keyTranslationMonitoringClient;
	@Inject @Named(HealthMonitoringClient.OR)
	private HealthMonitoringClient orchestratorMonitoringClient;

	/**
	 * Initializes clients.
	 */
	@PostConstruct
	void initializeClientMap() {
        clientMap.put(HealthMonitoringClient.AU, authenticationMonitoringClient);
        clientMap.put(HealthMonitoringClient.EA, extendedAuthenticationMonitoringClient);
		clientMap.put(HealthMonitoringClient.EI, electionInformationMontoringClient);
		clientMap.put(HealthMonitoringClient.VM, voterMaterialMonitoringClient);
		clientMap.put(HealthMonitoringClient.VV, voteVerificationMonitoringClient);
		clientMap.put(HealthMonitoringClient.VW, votingWorkflowMonitoringClient);
		clientMap.put(HealthMonitoringClient.CR, certificateRegistryMonitoringClient);
		clientMap.put(HealthMonitoringClient.KT, keyTranslationMonitoringClient);
		clientMap.put(HealthMonitoringClient.OR, orchestratorMonitoringClient);
	}

	/**
	 * Performs a check for all contexts.
	 *
	 * @return Returns a 200 response code with the result of the status of all contexts.
	 */
	@Path(CHECK_PLATFORM_HEALTH)
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	public Response checkPlatformHealth() {
        //for each context/client check the context health, in parallel if possible.
        Map<String, HealthCheckStatus> statusMap = clientMap.entrySet().stream().parallel().collect(
            Collectors.toMap(Map.Entry::getKey, e -> check(e.getKey(), e.getValue())));

        //if we encounter problems serializing the statuses map we can use GenericEntity to help with the serialization
		//and we can also use it in the client
        return Response.ok().entity(statusMap).build();
	}

	private HealthCheckStatus check(String contextName, HealthMonitoringClient client) {
		try {
			retrofit2.Response<HealthCheckStatus> executeCall = RetrofitConsumer.executeCall(client.checkHealth());
			return executeCall.body();
		} catch (RetrofitException e) {
			return tryHandleAsHealthCheck(e, contextName);
		}
	}

	private HealthCheckStatus tryHandleAsHealthCheck(final RetrofitException rfE, final String contextName) {
		if(rfE.getHttpCode() == 503 && rfE.getErrorBody() != null && rfE.getErrorBody().contentLength() != 0) {
			try (InputStream errorStream = rfE.getErrorBody().byteStream()){
				return ObjectMappers.fromJson(errorStream, HealthCheckStatus.class);
			} catch (IOException ioE) {
				LOG.error(ioE.getMessage(), ioE);
				LOG.error("Error trying to consume error message for checking the health of context " + contextName, rfE);
			}
		} else { 
			LOG.error("Error trying to call to end-point for checking the health of context " + contextName, rfE);
		}
		LOG.error("Error trying to call to end-point for checking the health of context " + contextName, rfE);
		return new HealthCheckStatus(Collections.singletonMap(HealthCheckValidationType.STATUS,
			HealthCheck.HealthCheckResult.unhealthy(rfE.getMessage())));
	}
}
