package com.red.circle.framework.shardingsphere.driver;

import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import org.apache.shardingsphere.driver.jdbc.core.driver.ShardingSphereDriverURLProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class NacosDriverURLProvider implements ShardingSphereDriverURLProvider {

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

  private static final String NACOS_TYPE = "nacos:";

  private static final String GROUP = "group";

  private static final long TIMEOUT_MILLIS = 6000L;

  @Override
  public boolean accept(String url) {
    return url != null && url.contains(NACOS_TYPE);
  }

  @Override
  public byte[] getContent(String url) {
    if (!hasText(url)) {
      throw new IllegalArgumentException("url isBlank.");
    }

    String shardingConfigFilename = getShardingConfigFilename(url);
    if (!hasText(shardingConfigFilename)) {
      throw new IllegalArgumentException("sharding config error.");
    }

    Map<String, String> queryParams = parseQuery(url);
    Properties properties = new Properties();
    setIfHasText(properties, "serverAddr", queryParams.get("serverAddr"));
    setIfHasText(properties, "namespace", queryParams.get("namespace"));
    setIfHasText(properties, "username", queryParams.get("username"));
    setIfHasText(properties, "password", queryParams.get("password"));

    String group = Optional.ofNullable(queryParams.get(GROUP))
        .filter(NacosDriverURLProvider::hasText)
        .orElse("DEFAULT_GROUP");

    try {
      LOGGER.info(
          "Load sharding config from nacos. url={}, dataId={}, group={}, queryParams={}",
          url,
          shardingConfigFilename,
          group,
          queryParams);
      ConfigService configService = NacosFactory.createConfigService(properties);
      String content = configService.getConfig(shardingConfigFilename, group, TIMEOUT_MILLIS);
      if (!hasText(content)) {
        throw new RuntimeException(
            "Empty Nacos sharding config: dataId=" + shardingConfigFilename + ", group=" + group);
      }
      LOGGER.info(content);
      return content.getBytes(StandardCharsets.UTF_8);
    } catch (NacosException ex) {
      throw new RuntimeException(ex);
    }
  }

  private static void setIfHasText(Properties properties, String key, String value) {
    if (hasText(value)) {
      properties.setProperty(key, value);
    }
  }

  private static boolean hasText(String value) {
    return value != null && !value.trim().isEmpty();
  }

  private Map<String, String> parseQuery(String url) {
    Map<String, String> queryParams = new HashMap<>();
    int queryIndex = url.indexOf('?');
    if (queryIndex < 0 || queryIndex + 1 >= url.length()) {
      return queryParams;
    }

    String query = url.substring(queryIndex + 1);
    for (String pair : query.split("&")) {
      if (pair.isEmpty()) {
        continue;
      }
      int separatorIndex = pair.indexOf('=');
      if (separatorIndex < 0) {
        queryParams.put(decode(pair), "");
        continue;
      }
      String key = decode(pair.substring(0, separatorIndex));
      String value = decode(pair.substring(separatorIndex + 1));
      queryParams.put(key, value);
    }
    return queryParams;
  }

  private String decode(String value) {
    return URLDecoder.decode(value, StandardCharsets.UTF_8);
  }

  private String getShardingConfigFilename(String url) {
    int nacosIndex = url.indexOf(NACOS_TYPE);
    if (nacosIndex < 0) {
      return "";
    }
    return url.substring(nacosIndex + NACOS_TYPE.length()).split("\\?")[0];
  }
}
