package ervu_business_metrics.service;

import java.lang.invoke.MethodHandles;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import ervu_business_metrics.config.KafkaEnabledCondition;
import ervu_business_metrics.kafka.model.DeleteKafkaMessage;
import ervu_business_metrics.kafka.model.UpsertMessage;
import ervu_business_metrics.model.idm.AccountData;
import ervu_business_metrics.model.idm.DomainData;
import ervu_business_metrics.model.idm.RoleData;
import ervu_business_metrics.service.processor.impl.AccountDataProcessor;
import ervu_business_metrics.service.processor.DataProcessor;
import ervu_business_metrics.service.processor.impl.DomainDataProcessor;
import ervu_business_metrics.service.processor.impl.RoleDataProcessor;
import exception.IdmDirectoriesException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Caching;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.DependsOn;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

/**
 * @author Adel Kalimullin
 */
@Component
@DependsOn("liquibase")
@Conditional(KafkaEnabledCondition.class)
public class IdmDirectoriesService {
  private static final Logger LOGGER = LoggerFactory.getLogger(
      MethodHandles.lookup().lookupClass());
  private final RestTemplate restTemplate;
  private final ObjectMapper objectMapper;
  private final Map<Class<?>, DataProcessor<?>> dataProcessors = new HashMap<>();
  @Value("${ervu.idm.url}")
  private String idmUrl;
  @Value("${ervu.directories:domain, role , account }")
  private String ervuDirectories;

  public IdmDirectoriesService(
      RestTemplate restTemplate,
      AccountDataProcessor accountDataProcessor,
      DomainDataProcessor domainDataProcessor,
      RoleDataProcessor roleDataProcessor, ObjectMapper objectMapper) {
    this.restTemplate = restTemplate;
    this.objectMapper = objectMapper;
    dataProcessors.put(AccountData.class, accountDataProcessor);
    dataProcessors.put(DomainData.class, domainDataProcessor);
    dataProcessors.put(RoleData.class, roleDataProcessor);
  }

  @Caching(evict = {
      @CacheEvict(value = "domain-ids", allEntries = true),
      @CacheEvict(value = "role-ids", allEntries = true),
      @CacheEvict(value = "account-ids", allEntries = true)
  })
  public void updateDirectories() {
    try {
      String[] ervuDirectoriesArray = ervuDirectories.split(",");
      Arrays.stream(ervuDirectoriesArray).forEach(ervuDirectory -> {
        String targetUrl = idmUrl + "/reconcile/" + ervuDirectory.trim() + "/to/kafka/v1";
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        String emptyJson = "{}";
        HttpEntity<String> requestEntity = new HttpEntity<>(emptyJson, headers);
        ResponseEntity<String> response = restTemplate.postForEntity(targetUrl, requestEntity,
            String.class
        );
        if (!response.getStatusCode().is2xxSuccessful()) {
          LOGGER.error("Error in {} request. Status code: {}; Body: {}",
              ervuDirectory, response.getStatusCode(), response.getBody()
          );
        }
      });
    }
    catch (Exception e) {
      LOGGER.error(e.getMessage());
      throw new IdmDirectoriesException(e);
    }
  }

  @Transactional
  public <T> void processUpsertMessage(String kafkaMessage, Class<T> entityClass) {
    try {
      JavaType messageType = objectMapper.getTypeFactory()
          .constructParametricType(UpsertMessage.class, entityClass);
      JavaType arrayType = objectMapper.getTypeFactory()
          .constructArrayType(messageType);

      UpsertMessage<T>[] messages = objectMapper.readValue(kafkaMessage, arrayType);
      if (messages.length > 0 && messages[0].getData() != null && !messages[0].getData()
          .isEmpty()) {
        DataProcessor<T> processor = (DataProcessor<T>) dataProcessors.get(entityClass);
        if (processor == null) {
          throw new IllegalStateException("No processor found for " + entityClass.getSimpleName());
        }

        processor.upsertData(messages[0].getData());
      }
    }
    catch (Exception e) {
      throw new IdmDirectoriesException(e);
    }
  }

  @Transactional
  public <T> void processDeleteMessage(String kafkaMessage, Class<T> entityClass) {
    try {
      DeleteKafkaMessage[] deleteKafkaMessages = objectMapper.readValue(kafkaMessage,
          DeleteKafkaMessage[].class
      );

      if (Boolean.TRUE.equals(deleteKafkaMessages[0].isSuccess())
          && deleteKafkaMessages[0].getData() != null && !deleteKafkaMessages[0].getData()
          .isEmpty()) {
        DataProcessor<T> processor = (DataProcessor<T>) dataProcessors.get(entityClass);
        if (processor == null) {
          throw new IllegalStateException("No processor found for " + entityClass.getSimpleName());
        }

        processor.deleteData(deleteKafkaMessages[0].getData());
      }

    }
    catch (Exception e) {
      throw new IdmDirectoriesException(e);
    }
  }
}
