package io.aiven.klaw.service;

import static io.aiven.klaw.error.KlawErrorMessages.SERVER_CONFIG_ERR_101;
import static io.aiven.klaw.error.KlawErrorMessages.SERVER_CONFIG_ERR_102;
import static io.aiven.klaw.error.KlawErrorMessages.SERVER_CONFIG_ERR_103;
import static io.aiven.klaw.error.KlawErrorMessages.SERVER_CONFIG_ERR_104;
import static io.aiven.klaw.error.KlawErrorMessages.SERVER_CONFIG_ERR_105;
import static io.aiven.klaw.error.KlawErrorMessages.SERVER_CONFIG_ERR_106;
import static io.aiven.klaw.helpers.KwConstants.CLUSTER_CONN_URL_KEY;
import static io.aiven.klaw.service.UsersTeamsControllerService.MASKED_PWD;
import static org.springframework.beans.BeanUtils.copyProperties;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import io.aiven.klaw.config.ManageDatabase;
import io.aiven.klaw.dao.Env;
import io.aiven.klaw.dao.KwProperties;
import io.aiven.klaw.error.KlawException;
import io.aiven.klaw.helpers.KwConstants;
import io.aiven.klaw.model.ApiResponse;
import io.aiven.klaw.model.KwPropertiesModel;
import io.aiven.klaw.model.KwTenantConfigModel;
import io.aiven.klaw.model.ServerConfigProperties;
import io.aiven.klaw.model.TenantConfig;
import io.aiven.klaw.model.enums.ApiResultStatus;
import io.aiven.klaw.model.enums.ClusterStatus;
import io.aiven.klaw.model.enums.EntityType;
import io.aiven.klaw.model.enums.MetadataOperationType;
import io.aiven.klaw.model.enums.PermissionType;
import io.aiven.klaw.model.response.ConnectivityStatus;
import io.aiven.klaw.model.response.KwPropertiesResponse;
import jakarta.annotation.PostConstruct;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.text.WordUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class ServerConfigService {

  public static final ObjectMapper OBJECT_MAPPER =
      new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
  public static final ObjectWriter WRITER_WITH_DEFAULT_PRETTY_PRINTER =
      OBJECT_MAPPER.writerWithDefaultPrettyPrinter();
  @Autowired private Environment env;

  @Autowired ManageDatabase manageDatabase;

  @Autowired private MailUtils mailService;

  @Autowired private ClusterApiService clusterApiService;

  private final CommonUtilsService commonUtilsService;

  private static Map<String, ServerConfigProperties> key2Props;

  public ServerConfigService(
      Environment env,
      CommonUtilsService commonUtilsService,
      MailUtils mailService,
      ManageDatabase manageDatabase) {
    this.env = env;
    this.commonUtilsService = commonUtilsService;
    this.mailService = mailService;
    this.manageDatabase = manageDatabase;
  }

  @PostConstruct
  public void getAllProperties() {
    log.debug("All server properties being loaded");
    Map<String, ServerConfigProperties> key2Props = new HashMap<>();
    List<String> allowedKeys = Arrays.asList("spring.", "klaw.");

    if (env instanceof ConfigurableEnvironment) {
      for (PropertySource<?> propertySource :
          ((ConfigurableEnvironment) env).getPropertySources()) {
        if (propertySource instanceof EnumerablePropertySource) {
          for (String key : ((EnumerablePropertySource<?>) propertySource).getPropertyNames()) {

            ServerConfigProperties props = new ServerConfigProperties();
            props.setKey(key);
            if (key.contains("password")
                || key.contains("license")
                || key.contains("pwd")
                || key.contains("cert")
                || key.contains("secret")) {
              props.setValue(MASKED_PWD);
            } else {
              props.setValue(WordUtils.wrap(propertySource.getProperty(key) + "", 125, "\n", true));
            }
            if (!key2Props.containsKey(key)
                && !key.toLowerCase().contains("path")
                && !key.contains("secretkey")
                && !key.contains("password")
                && !key.contains("username")) {
              if (allowedKeys.stream().anyMatch(key::startsWith)) {
                key2Props.put(props.getKey(), props);
              }
            }
          }
        }
      }
    }
    ServerConfigService.key2Props = key2Props;
  }

  public Collection<ServerConfigProperties> getAllProps() {
    if (commonUtilsService.isNotAuthorizedUser(
        commonUtilsService.getPrincipal(), PermissionType.UPDATE_SERVERCONFIG)) {
      return new ArrayList<>();
    }
    return key2Props.values();
  }

  public List<KwPropertiesResponse> getAllEditableProps() {
    List<KwPropertiesResponse> listMap = new ArrayList<>();
    KwPropertiesResponse propertiesResponse = new KwPropertiesResponse();

    if (commonUtilsService.isNotAuthorizedUser(
        commonUtilsService.getPrincipal(), PermissionType.UPDATE_SERVERCONFIG)) {
      propertiesResponse.setResult(ApiResultStatus.NOT_AUTHORIZED.value);
      listMap.add(propertiesResponse);
      return listMap;
    }

    int tenantId = commonUtilsService.getTenantId(getUserName());
    Map<String, Map<String, String>> kwProps = manageDatabase.getKwPropertiesMap(tenantId);
    String kwVal, kwKey;

    for (Map.Entry<String, Map<String, String>> stringStringEntry : kwProps.entrySet()) {
      KwPropertiesResponse kwPropertiesResponse = new KwPropertiesResponse();
      kwKey = stringStringEntry.getKey();
      kwVal = stringStringEntry.getValue().get("kwvalue");
      kwPropertiesResponse.setKwkey(kwKey);

      if (KwConstants.TENANT_CONFIG_PROPERTY.equals(kwKey)) {
        TenantConfig dynamicObj;
        try {
          dynamicObj = OBJECT_MAPPER.readValue(kwVal, TenantConfig.class);
          updateEnvNameValues(dynamicObj, tenantId);
          kwVal = WRITER_WITH_DEFAULT_PRETTY_PRINTER.writeValueAsString(dynamicObj);
          kwPropertiesResponse.setKwvalue(kwVal);
          kwPropertiesResponse.setKwdesc(stringStringEntry.getValue().get("kwdesc"));

          listMap.add(kwPropertiesResponse);
        } catch (Exception ioe) {
          log.error("Error from getAllEditableProps {}", kwKey, ioe);
          log.error("No environments/clusters found. {}", kwKey);
          kwVal = "{}";
          kwPropertiesResponse.setKwvalue(kwVal);
          kwPropertiesResponse.setKwdesc(stringStringEntry.getValue().get("kwdesc"));
        }
      } else {
        kwPropertiesResponse.setKwvalue(kwVal);
        kwPropertiesResponse.setKwdesc(stringStringEntry.getValue().get("kwdesc"));

        listMap.add(kwPropertiesResponse);
      }
    }

    if (tenantId != KwConstants.DEFAULT_TENANT_ID) {
      return listMap.stream()
          .filter(item -> KwConstants.allowConfigForAdmins.contains(item.getKwkey()))
          .collect(Collectors.toList());
    } else {
      return listMap;
    }
  }

  public ApiResponse updateKwCustomProperty(KwPropertiesModel kwPropertiesModel)
      throws KlawException {
    log.info("updateKwCustomProperty {}", kwPropertiesModel);
    int tenantId = commonUtilsService.getTenantId(getUserName());
    String kwKey = kwPropertiesModel.getKwKey();
    String kwVal = kwPropertiesModel.getKwValue().trim();

    if (commonUtilsService.isNotAuthorizedUser(
        commonUtilsService.getPrincipal(), PermissionType.UPDATE_SERVERCONFIG)) {
      return ApiResponse.NOT_AUTHORIZED;
    }

    // SUPERADMINS filter
    if (tenantId != KwConstants.DEFAULT_TENANT_ID) {
      if (!KwConstants.allowConfigForAdmins.contains(kwKey)) {
        return ApiResponse.NOT_AUTHORIZED;
      }
    }

    try {
      if (KwConstants.TENANT_CONFIG_PROPERTY.equals(kwKey)) {
        TenantConfig dynamicObj;
        try {
          dynamicObj = OBJECT_MAPPER.readValue(kwVal, TenantConfig.class);
          if (validateTenantConfig(dynamicObj, tenantId)) {
            updateEnvIdValues(dynamicObj);
            kwPropertiesModel.setKwValue(OBJECT_MAPPER.writeValueAsString(dynamicObj));
          } else {
            return ApiResponse.builder()
                .success(false)
                .message(SERVER_CONFIG_ERR_101)
                .data(kwKey)
                .build();
          }
        } catch (IOException e) {
          log.error("Exception:", e);
          return ApiResponse.builder()
              .success(false)
              .message(SERVER_CONFIG_ERR_102)
              .data(kwKey)
              .build();
        }
      }
    } catch (KlawException klawException) {
      return ApiResponse.builder()
          .success(false)
          .message(klawException.getMessage())
          .data(kwKey)
          .build();

    } catch (Exception e) {
      log.error("Exception:", e);
      return ApiResponse.builder()
          .success(false)
          .message(SERVER_CONFIG_ERR_103)
          .data(kwKey)
          .build();
    }

    try {
      KwProperties kwProperties = new KwProperties();
      copyProperties(kwPropertiesModel, kwProperties);
      String res = manageDatabase.getHandleDbRequests().updateKwProperty(kwProperties, tenantId);
      if (ApiResultStatus.SUCCESS.value.equals(res)) {
        commonUtilsService.updateMetadata(
            tenantId, EntityType.PROPERTIES, MetadataOperationType.CREATE, null);
        return ApiResponse.builder()
            .success(true)
            .message(ApiResultStatus.SUCCESS.value)
            .data(kwKey)
            .build();
      } else {
        return ApiResponse.FAILURE;
      }
    } catch (Exception e) {
      throw new KlawException(e.getMessage());
    }
  }

  private void updateEnvNameValues(TenantConfig dynamicObj, int tenantId) {
    if (dynamicObj.getTenantModel() != null) {
      KwTenantConfigModel tenant = dynamicObj.getTenantModel();

      // syncClusterName update
      if (tenant.getBaseSyncEnvironment() != null) {
        tenant.setBaseSyncEnvironment(
            getEnvDetails(tenant.getBaseSyncEnvironment(), tenantId).getName());
      }

      // syncClusterKafkaConnectName update
      if (tenant.getBaseSyncKafkaConnectCluster() != null) {
        tenant.setBaseSyncKafkaConnectCluster(
            getKafkaConnectEnvDetails(tenant.getBaseSyncKafkaConnectCluster(), tenantId).getName());
      }

      // kafka
      if (tenant.getOrderOfTopicPromotionEnvsList() != null) {
        List<String> tmpOrderList = new ArrayList<>();
        tenant
            .getOrderOfTopicPromotionEnvsList()
            .forEach(
                a -> {
                  if (getEnvDetails(a, tenantId) != null)
                    tmpOrderList.add(getEnvDetails(a, tenantId).getName());
                });
        tenant.setOrderOfTopicPromotionEnvsList(tmpOrderList);
      }

      // kafkaconnect
      if (tenant.getOrderOfConnectorsPromotionEnvsList() != null) {
        List<String> tmpOrderList1 = new ArrayList<>();
        tenant
            .getOrderOfConnectorsPromotionEnvsList()
            .forEach(
                a -> {
                  if (getKafkaConnectEnvDetails(a, tenantId) != null) {
                    tmpOrderList1.add(getKafkaConnectEnvDetails(a, tenantId).getName());
                  }
                });
        tenant.setOrderOfConnectorsPromotionEnvsList(tmpOrderList1);
      }

      // kafka
      if (tenant.getRequestTopicsEnvironmentsList() != null) {
        List<String> tmpReqTopicList = new ArrayList<>();
        tenant
            .getRequestTopicsEnvironmentsList()
            .forEach(
                a -> {
                  if (getEnvDetails(a, tenantId) != null) {
                    tmpReqTopicList.add(getEnvDetails(a, tenantId).getName());
                  }
                });
        tenant.setRequestTopicsEnvironmentsList(tmpReqTopicList);
      }

      // kafkaconnect
      if (tenant.getRequestConnectorsEnvironmentsList() != null) {
        List<String> tmpReqTopicList1 = new ArrayList<>();
        tenant
            .getRequestConnectorsEnvironmentsList()
            .forEach(
                a -> {
                  if (getKafkaConnectEnvDetails(a, tenantId) != null) {
                    tmpReqTopicList1.add(getKafkaConnectEnvDetails(a, tenantId).getName());
                  }
                });
        tenant.setRequestConnectorsEnvironmentsList(tmpReqTopicList1);
      }
      // Schema tenant Config deprecated

    }
  }

  private void updateEnvIdValues(TenantConfig dynamicObj) throws KlawException {
    if (dynamicObj.getTenantModel() != null) {
      KwTenantConfigModel tenantModel = dynamicObj.getTenantModel();

      // syncClusterName update
      Integer tenantId = getTenantIdFromName(tenantModel.getTenantName());
      if (tenantModel.getBaseSyncEnvironment() != null) {
        tenantModel.setBaseSyncEnvironment(
            getEnvDetailsFromName(tenantModel.getBaseSyncEnvironment(), tenantId).getId());
      }

      // syncClusterKafkaConnectName update
      if (tenantModel.getBaseSyncKafkaConnectCluster() != null) {
        tenantModel.setBaseSyncKafkaConnectCluster(
            getKafkaConnectEnvDetailsFromName(
                    tenantModel.getBaseSyncKafkaConnectCluster(), tenantId)
                .getId());
      }

      // kafka
      if (tenantModel.getOrderOfTopicPromotionEnvsList() != null) {
        List<String> tmpOrderList = new ArrayList<>();
        List<String> topicPromotionEnvs = tenantModel.getOrderOfTopicPromotionEnvsList();
        topicPromotionEnvs.forEach(
            a -> tmpOrderList.add(getEnvDetailsFromName(a, tenantId).getId()));
        if (!validateEnvsClusters(topicPromotionEnvs, tenantId)) {
          throw new KlawException(SERVER_CONFIG_ERR_106);
        }
        tenantModel.setOrderOfTopicPromotionEnvsList(tmpOrderList);
      }

      // kafkaconnect
      if (tenantModel.getOrderOfConnectorsPromotionEnvsList() != null) {
        List<String> tmpOrderList1 = new ArrayList<>();
        tenantModel
            .getOrderOfConnectorsPromotionEnvsList()
            .forEach(
                a -> tmpOrderList1.add(getKafkaConnectEnvDetailsFromName(a, tenantId).getId()));
        tenantModel.setOrderOfConnectorsPromotionEnvsList(tmpOrderList1);
      }

      // kafka
      if (tenantModel.getRequestTopicsEnvironmentsList() != null) {
        List<String> tmpReqTopicList = new ArrayList<>();
        tenantModel
            .getRequestTopicsEnvironmentsList()
            .forEach(a -> tmpReqTopicList.add(getEnvDetailsFromName(a, tenantId).getId()));
        tenantModel.setRequestTopicsEnvironmentsList(tmpReqTopicList);
      }

      // kafkaconnect
      if (tenantModel.getRequestConnectorsEnvironmentsList() != null) {
        List<String> tmpReqTopicList1 = new ArrayList<>();
        tenantModel
            .getRequestConnectorsEnvironmentsList()
            .forEach(
                a -> tmpReqTopicList1.add(getKafkaConnectEnvDetailsFromName(a, tenantId).getId()));
        tenantModel.setRequestConnectorsEnvironmentsList(tmpReqTopicList1);
      }

      // Schema TenantConfig deprecated

    }
  }

  // verify is same kafka clusters are configured for the topic promotion envs
  private boolean validateEnvsClusters(List<String> topicPromotionEnvs, Integer tenantId) {
    Set<Integer> uniqueClusterIds = new HashSet<>();
    topicPromotionEnvs.forEach(
        env -> uniqueClusterIds.add(getEnvDetailsFromName(env, tenantId).getClusterId()));
    return uniqueClusterIds.size() == topicPromotionEnvs.size();
  }

  private boolean validateTenantConfig(TenantConfig dynamicObj, int tenantId) throws KlawException {
    Map<Integer, String> tenantMap = manageDatabase.getTenantMap();
    List<Env> envList = manageDatabase.getKafkaEnvList(tenantId);
    List<Env> envKafkaConnectList = manageDatabase.getKafkaConnectEnvList(tenantId);

    List<String> envListStr = new ArrayList<>();
    envList.forEach(a -> envListStr.add(a.getName()));

    List<String> envListKafkaConnectStr = new ArrayList<>();
    envKafkaConnectList.forEach(a -> envListKafkaConnectStr.add(a.getName()));

    boolean tenantCheck;
    try {
      tenantCheck = tenantMap.containsValue(dynamicObj.getTenantModel().getTenantName());

      if (tenantCheck) {
        KwTenantConfigModel tenantModel = dynamicObj.getTenantModel();
        // syncClusterCheck
        isBaseSyncValid(tenantModel.getBaseSyncEnvironment(), envListStr);
        // syncClusterKafkaConnectCheck
        isBaseSyncValid(tenantModel.getBaseSyncKafkaConnectCluster(), envListKafkaConnectStr);
        // orderOfenvs check
        isResourceAlreadyCreated(tenantModel.getOrderOfTopicPromotionEnvsList(), envListStr);
        // check that all Kafka Connectors in the ordered list already exist as resources.
        isResourceAlreadyCreated(
            tenantModel.getOrderOfConnectorsPromotionEnvsList(), envListKafkaConnectStr);
        // requestTopic Check
        isResourceAlreadyCreated(tenantModel.getRequestTopicsEnvironmentsList(), envListStr);
        // requestConnectors check
        isResourceAlreadyCreated(
            tenantModel.getRequestConnectorsEnvironmentsList(), envListKafkaConnectStr);
      }
    } catch (Exception e) {
      log.error(dynamicObj + "", e);
      throw e;
    }

    return tenantCheck;
  }

  private void isBaseSyncValid(String baseSync, List<String> existingResources)
      throws KlawException {
    if (baseSync != null && !existingResources.contains(baseSync)) {
      throw new KlawException(String.format(SERVER_CONFIG_ERR_104, baseSync));
    }
  }

  private void isResourceAlreadyCreated(List<String> namedResources, List<String> existingResources)
      throws KlawException {

    if (namedResources != null) {
      for (String res : namedResources) {
        if (!existingResources.contains(res)) {
          throw new KlawException(String.format(SERVER_CONFIG_ERR_105, res));
        }
      }
    }
  }

  public Env getEnvDetails(String envId, int tenantId) {
    Optional<Env> envFound =
        manageDatabase.getKafkaEnvList(tenantId).stream()
            .filter(env -> Objects.equals(env.getId(), envId))
            .findFirst();
    return envFound.orElse(null);
  }

  public Env getKafkaConnectEnvDetails(String envId, int tenantId) {
    Optional<Env> envFound =
        manageDatabase.getKafkaConnectEnvList(tenantId).stream()
            .filter(env -> Objects.equals(env.getId(), envId))
            .findFirst();
    return envFound.orElse(null);
  }

  public Env getSchemaEnvDetails(String envId, int tenantId) {
    Optional<Env> envFound =
        manageDatabase.getSchemaRegEnvList(tenantId).stream()
            .filter(env -> Objects.equals(env.getId(), envId))
            .findFirst();
    return envFound.orElse(null);
  }

  public Env getEnvDetailsFromName(String envName, Integer tenantId) {
    Optional<Env> envFound =
        manageDatabase.getKafkaEnvList(tenantId).stream()
            .filter(
                env ->
                    Objects.equals(env.getName(), envName)
                        && Objects.equals(env.getTenantId(), tenantId))
            .findFirst();
    return envFound.orElse(null);
  }

  public Env getSchemaEnvDetailsFromName(String envName, Integer tenantId) {
    Optional<Env> envFound =
        manageDatabase.getSchemaRegEnvList(tenantId).stream()
            .filter(
                env ->
                    Objects.equals(env.getName(), envName)
                        && Objects.equals(env.getTenantId(), tenantId))
            .findFirst();
    return envFound.orElse(null);
  }

  public Env getKafkaConnectEnvDetailsFromName(String envName, Integer tenantId) {
    Optional<Env> envFound =
        manageDatabase.getKafkaConnectEnvList(tenantId).stream()
            .filter(env -> env.getName().equals(envName) && env.getTenantId().equals(tenantId))
            .findFirst();
    return envFound.orElse(null);
  }

  public Map<String, String> resetCache() {
    Map<String, String> hashMap = new HashMap<>();
    manageDatabase.updateStaticDataForTenant(commonUtilsService.getTenantId(getUserName()));
    return hashMap;
  }

  public ConnectivityStatus testClusterApiConnection(String clusterApiUrl) throws KlawException {
    ConnectivityStatus connectivityStatus = new ConnectivityStatus();
    int tenantId = commonUtilsService.getTenantId(getUserName());
    ClusterStatus clusterApiStatus =
        clusterApiService.getClusterApiStatus(clusterApiUrl, true, tenantId);

    String apiResultStatus = "";
    if (ClusterStatus.ONLINE.equals(clusterApiStatus)) {
      apiResultStatus = ApiResultStatus.SUCCESS.value;
      KwPropertiesModel kwPropertiesModel = new KwPropertiesModel();
      kwPropertiesModel.setKwKey(CLUSTER_CONN_URL_KEY);
      kwPropertiesModel.setKwValue(clusterApiUrl);
      updateKwCustomProperty(kwPropertiesModel);
    } else {
      apiResultStatus = ApiResultStatus.FAILURE.value;
    }
    connectivityStatus.setConnectionStatus(apiResultStatus);
    return connectivityStatus;
  }

  private Integer getTenantIdFromName(String tenantName) {
    return manageDatabase.getTenantMap().entrySet().stream()
        .filter(obj -> Objects.equals(obj.getValue(), tenantName))
        .findFirst()
        .get()
        .getKey();
  }

  private String getUserName() {
    return mailService.getUserName(commonUtilsService.getPrincipal());
  }
}
