package com.red.circle.other.infra.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.red.circle.component.redis.service.RedisService;
import com.red.circle.tool.core.text.StringUtils;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;

/**
 * IP 归属国家查询工具.
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class IpCountryUtils {

  private static final String REDIS_KEY_PREFIX = "IP_COUNTRY_V2:";
  private static final List<String> ALLOWED_CHINA_REGIONS = List.of(
      "香港", "澳门", "澳門", "台湾", "台灣", "Hong Kong", "Macau", "Macao", "Taiwan"
  );
  private static final List<String> BLOCKED_COUNTRY_CODES = List.of(
      "HK", "MO", "TW", "SG"
  );
  private static final List<String> BLOCKED_COUNTRY_NAMES = List.of(
      "香港", "澳门", "澳門", "台湾", "台灣", "新加坡",
      "Hong Kong", "Macau", "Macao", "Taiwan", "Singapore"
  );
  private static final List<IpGeoProvider> IP_GEO_PROVIDERS = List.of(
      new IpGeoProvider("ip.sb", "https://api.ip.sb/geoip/%s"),
      new IpGeoProvider("freeipapi", "https://freeipapi.com/api/json/%s"),
      new IpGeoProvider("ipwhois", "https://ipwhois.app/json/%s?format=json"),
      new IpGeoProvider("ip.nc.gy", "https://ip.nc.gy/json?ip=%s")
  );

  private final RedisService redisService;

  public IpGeoData getGeoData(String ip) {
    if (StringUtils.isBlank(ip) || Objects.equals("-", ip)) {
      return IpGeoData.empty();
    }
    String redisKey = REDIS_KEY_PREFIX + ip;
    String cached = redisService.getString(redisKey);
    if (StringUtils.isNotBlank(cached)) {
      return IpGeoData.fromJson(JSONObject.parseObject(cached));
    }

    RequestConfig requestConfig = RequestConfig.custom()
        .setConnectTimeout(5000)
        .setSocketTimeout(5000)
        .setConnectionRequestTimeout(5000)
        .build();

    try (CloseableHttpClient httpClient = HttpClients.custom()
        .setDefaultRequestConfig(requestConfig)
        .setRedirectStrategy(new LaxRedirectStrategy())
        .build()) {
      for (IpGeoProvider provider : IP_GEO_PROVIDERS) {
        IpGeoData data = requestIpGeoData(httpClient, provider, ip);
        if (!data.isEmpty()) {
          redisService.setString(redisKey, data.toJson().toJSONString());
          return data;
        }
      }
    } catch (Exception e) {
      log.error("IpCountryUtils load ip geo data error ip={}, message={}", ip, e.getMessage());
    }
    return IpGeoData.empty();
  }

  /**
   * 根据 IP 获取国家二字码（如 US、CN、HK）.
   * 结果缓存到 Redis，永久有效（IP 归属地基本不变）.
   *
   * @param ip 用户 IP
   * @return 国家二字码，查询失败返回空字符串
   */
  public String getCountryCode(String ip) {
    return getGeoData(ip).countryCode();
  }

  /**
   * 判断 IP 是否属于美国.
   */
  public boolean isUsIp(String ip) {
    return "US".equalsIgnoreCase(getCountryCode(ip));
  }

  public boolean isMainlandChina(String ip) {
    return isMainlandChina(getGeoData(ip));
  }

  public static boolean isBlockedIpRegion(IpGeoData data) {
    return isMainlandChina(data) || isBlockedCountryOrRegion(data);
  }

  public static boolean isMainlandChina(IpGeoData data) {
    if (data == null || data.isEmpty()) {
      return false;
    }
    boolean china = "CN".equalsIgnoreCase(data.countryCode())
        || "中国".equals(data.countryName())
        || "China".equalsIgnoreCase(data.countryName());
    if (!china) {
      return false;
    }
    return StringUtils.isBlank(data.region()) || !ALLOWED_CHINA_REGIONS.contains(data.region());
  }

  private static boolean isBlockedCountryOrRegion(IpGeoData data) {
    if (data == null || data.isEmpty()) {
      return false;
    }
    if (StringUtils.isNotBlank(data.countryCode())
        && BLOCKED_COUNTRY_CODES.contains(data.countryCode().trim().toUpperCase(Locale.ROOT))) {
      return true;
    }
    return BLOCKED_COUNTRY_NAMES.contains(data.countryName())
        || BLOCKED_COUNTRY_NAMES.contains(data.region());
  }

  private IpGeoData requestIpGeoData(CloseableHttpClient httpClient, IpGeoProvider provider, String ip) {
    String requestUrl = provider.buildUrl(ip);
    HttpGet httpGet = new HttpGet(requestUrl);
    try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
      int statusCode = response.getStatusLine().getStatusCode();
      String result = EntityUtils.toString(response.getEntity());
      log.info("IpCountryUtils request provider={} url={} status={} result={}", provider.name, requestUrl, statusCode, result);
      if (statusCode < 200 || statusCode >= 300 || StringUtils.isBlank(result)) {
        return IpGeoData.empty();
      }
      return normalizeIpData(provider.name, JSON.parseObject(result));
    } catch (Exception e) {
      log.warn("IpCountryUtils request failed provider={} ip={} message={}", provider.name, ip, e.getMessage());
      return IpGeoData.empty();
    }
  }

  static IpGeoData normalizeIpData(String provider, JSONObject jsonObject) {
    if (jsonObject == null) {
      return IpGeoData.empty();
    }
    return switch (provider) {
      case "ip.sb" -> buildIpData(
          provider,
          jsonObject.getString("country_code"),
          jsonObject.getString("country"),
          jsonObject.getString("region")
      );
      case "freeipapi" -> buildIpData(
          provider,
          jsonObject.getString("countryCode"),
          jsonObject.getString("countryName"),
          jsonObject.getString("regionName")
      );
      case "ipwhois" -> Boolean.FALSE.equals(jsonObject.getBoolean("success"))
          ? IpGeoData.empty()
          : buildIpData(
              provider,
              jsonObject.getString("country_code"),
              jsonObject.getString("country"),
              jsonObject.getString("region")
          );
      case "ip.nc.gy" -> normalizeIpNcGy(provider, jsonObject);
      default -> IpGeoData.empty();
    };
  }

  private static IpGeoData normalizeIpNcGy(String provider, JSONObject jsonObject) {
    JSONObject countryObject = jsonObject.getJSONObject("country");
    if (countryObject == null) {
      countryObject = jsonObject.getJSONObject("registered_country");
    }
    if (countryObject == null) {
      return IpGeoData.empty();
    }
    String region = null;
    JSONArray subdivisions = jsonObject.getJSONArray("subdivisions");
    if (subdivisions != null && !subdivisions.isEmpty()) {
      JSONObject subdivision = subdivisions.getJSONObject(0);
      if (subdivision != null) {
        region = getName(subdivision.getJSONObject("names"));
      }
    }
    return buildIpData(
        provider,
        countryObject.getString("iso_code"),
        getName(countryObject.getJSONObject("names")),
        region
    );
  }

  private static IpGeoData buildIpData(String provider, String countryCode, String countryName, String region) {
    if (StringUtils.isBlank(countryCode) && StringUtils.isBlank(countryName)) {
      return IpGeoData.empty();
    }
    String normalizedCountryCode = StringUtils.isBlank(countryCode)
        ? ""
        : countryCode.trim().toUpperCase(Locale.ROOT);
    return new IpGeoData(normalizedCountryCode, Objects.toString(countryName, ""), Objects.toString(region, ""), provider);
  }

  private static String getName(JSONObject names) {
    if (names == null) {
      return "";
    }
    String name = names.getString("zh-CN");
    if (StringUtils.isNotBlank(name)) {
      return name;
    }
    return Objects.toString(names.getString("en"), "");
  }

  private record IpGeoProvider(String name, String urlPattern) {

    String buildUrl(String ip) {
      return String.format(urlPattern, ip);
    }
  }

  public record IpGeoData(String countryCode, String countryName, String region, String source) {

    public static IpGeoData empty() {
      return new IpGeoData("", "", "", "");
    }

    static IpGeoData fromJson(JSONObject jsonObject) {
      if (jsonObject == null) {
        return empty();
      }
      return new IpGeoData(
          Objects.toString(jsonObject.getString("countryCode"), ""),
          Objects.toString(jsonObject.getString("countryName"), ""),
          Objects.toString(jsonObject.getString("region"), ""),
          Objects.toString(jsonObject.getString("source"), "")
      );
    }

    JSONObject toJson() {
      JSONObject jsonObject = new JSONObject();
      jsonObject.put("countryCode", countryCode);
      jsonObject.put("countryName", countryName);
      jsonObject.put("region", region);
      jsonObject.put("source", source);
      return jsonObject;
    }

    public boolean isEmpty() {
      return StringUtils.isBlank(countryCode) && StringUtils.isBlank(countryName);
    }
  }
}
