package ru.micord.ervu.account_applications.service;

import java.lang.invoke.MethodHandles;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.DependsOn;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import ru.micord.ervu.account_applications.exception.IdmDirectoriesException;
import ru.micord.ervu.account_applications.kafka.model.ChangeActiveMessage;
import ru.micord.ervu.account_applications.kafka.model.DeleteLinkMessage;
import ru.micord.ervu.account_applications.kafka.model.UpsertMessage;
import ru.micord.ervu.account_applications.service.processor.DataProcessor;
import ru.micord.ervu.account_applications.service.processor.LinkDataProcessor;

/**
 * @author Eduard Tihomirov
 */
@Service
@DependsOn("liquibase")
public class ErvuDirectoriesService {
  private static final Logger LOGGER = LoggerFactory.getLogger(
      MethodHandles.lookup().lookupClass());
  private final RestTemplate restTemplate;
  private final ObjectMapper objectMapper;
  private final Map<Class<?>, DataProcessor<?, ?>> dataProcessors;
  @Value("${idm.url}")
  private String idmUrl;
  @Value("${ervu.directories:domain, role, solution}")
  private String ervuDirectories;

  public ErvuDirectoriesService(RestTemplate restTemplate, ObjectMapper objectMapper,
      List<DataProcessor<?, ?>> processors) {
    this.restTemplate = restTemplate;
    this.objectMapper = objectMapper;
    this.dataProcessors = processors.stream()
        .collect(Collectors.toMap(DataProcessor::getType, p -> p));
  }

  public void updateDirectories() {
    try {
      String[] ervuDirectoriesArray = ervuDirectories.split(",");
      Arrays.stream(ervuDirectoriesArray).forEach(ervuDirectory -> {
        String targetUrl = UriComponentsBuilder.fromHttpUrl(idmUrl)
            .pathSegment("reconcile", ervuDirectory.trim(), "to", "kafka", "v1")
            .toUriString();
        ResponseEntity<String> response = restTemplate.getForEntity(targetUrl, 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);

      UpsertMessage<T> message = objectMapper.readValue(kafkaMessage, messageType);

      if (message.isSuccess() && message.getData() != null) {
        DataProcessor<T, ?> processor = (DataProcessor<T, ?>) dataProcessors.get(entityClass);
        if (processor == null) {
          throw new IllegalStateException("No processor found for " + entityClass.getSimpleName());
        }

        processor.upsertData(message.getData());
      }
    }
    catch (Exception e) {
      throw new IdmDirectoriesException(e);
    }
  }

  @Transactional
  public <T> void processStatusChange(String kafkaMessage, Class<T> entityClass, boolean active) {
    try {
      ChangeActiveMessage changeActiveMessage = objectMapper.readValue(kafkaMessage,
          ChangeActiveMessage.class
      );

      if (changeActiveMessage.isSuccess() &&
          changeActiveMessage.getData() != null &&
          !CollectionUtils.isEmpty(changeActiveMessage.getData().getIds())) {
        DataProcessor<T, ?> processor = (DataProcessor<T, ?>) dataProcessors.get(entityClass);
        if (processor == null) {
          throw new IllegalStateException("No processor found for " + entityClass.getSimpleName());
        }

        processor.changeActiveStatus(changeActiveMessage.getData().getIds(), active);
      }
    }
    catch (Exception e) {
      throw new IdmDirectoriesException(e);
    }
  }

  @Transactional
  public <T> void processDeleteLink(String kafkaMessage, Class<T> entityClass) {
    try {
      JavaType messageType = objectMapper.getTypeFactory()
          .constructParametricType(DeleteLinkMessage.class, entityClass);
      DeleteLinkMessage<T> message = objectMapper.readValue(kafkaMessage, messageType);

      if (message.isSuccess() && message.getData() != null) {
        DataProcessor<T, ?> processor = (DataProcessor<T, ?>) dataProcessors.get(entityClass);
        if (processor instanceof LinkDataProcessor<T, ?> linkProcessor) {
          linkProcessor.deleteLink(message.getData());
        }
        else {
          LOGGER.warn("Processor {} does not support deleteLink operation",
              entityClass.getSimpleName()
          );
        }
      }
    }
    catch (Exception e) {
      throw new IdmDirectoriesException(e);
    }
  }
}
