package com.seer.teach.common.service.impl;

import com.seer.teach.common.enums.ResultCodeEnum;
import com.seer.teach.common.resp.IpAddressResp;
import com.seer.teach.common.utils.AssertUtils;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.stereotype.Service;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static cn.hutool.core.lang.Validator.IPV4;

/**
 * <p>
 *
 * </p >
 *
 * @author Captain
 * @since 2025-08-12 16:58
 */
@Slf4j
@Service
public class Ip2RegionService {

    private String DB_PATH = "ip/ip2region.xdb";

    private Searcher searcher;

    /**
     * 初始化方法，在Spring容器启动时加载IP地址库文件，创建Searcher实例。
     * 若初始化失败，记录错误日志。
     */
    @PostConstruct
    public void init() {
        try {
            searcher = Searcher.newWithBuffer(getSearchDbContent());
            log.info("地域本地数据库 ip2region.xdb 初始成功 : {}", DB_PATH);
        } catch (IOException e) {
            log.error("地域本地数据库 ip2region.xdb 初始失败  `{}`", DB_PATH, e);
        }
    }

    /**
     * 销毁方法，在Spring容器关闭前释放Searcher资源。
     * 若关闭过程中出现异常，记录警告日志。
     */
    @PreDestroy
    public void destroy() {
        if (searcher != null) {
            try {
                searcher.close();
                log.info("地域本地数据库 关闭成功");
            } catch (IOException e) {
                log.warn("地域本地数据库 关闭失败", e);
            }
        }
    }

    /**
     * 根据IP地址查询对应的省市信息，并封装为IpAddressDTO对象返回。
     *
     * @param ip 需要查询的IP地址
     * @return 返回包含省份和城市信息的IpAddressDTO对象；若未找到或发生异常则返回null
     */
    public IpAddressResp getProvinceCityByIp(String ip) {
        if (searcher == null) {
            log.error("Searcher未初始化");
            return null;
        }
        try {
            long startTime = System.nanoTime();
            String region = searcher.search(ip);
            long costMicros = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - startTime);
            log.info("IP地址: {}, 区域信息: {}, IO次数: {}, 耗时: {} 微秒", ip, region, searcher.getIOCount(), costMicros);
            if (region.contains("内网IP")) {
                return new IpAddressResp("广东省", "中山市");
            }
            if (region == null || region.isEmpty()) {
                return null;
            }
            // 按照"国家|区域|省份|城市|ISP"格式解析region字符串
            String[] parts = region.split("\\|");
            if (parts.length >= 4) {
                String province = parts[2];
                String city = parts[3];
                if ("0".equals(province)) {
                    province = "";
                }
                if ("0".equals(city)) {
                    city = "";
                }
                return new IpAddressResp(province, city);
            }
        } catch (Exception e) {
            log.error("查询IP地址区域信息出错: {}", ip, e);
        }
        return null;
    }

    public String extractClientIp(HttpServletRequest request) {
        String xff = request.getHeader("X-Forwarded-For");
        if (xff != null && !xff.isEmpty() && !"unknown".equalsIgnoreCase(xff)) {
            for (String s : xff.split(",")) {
                String cand = s.trim().replace("\"", "");
                int colon = cand.indexOf(':');
                if (colon > 0) {
                    cand = cand.substring(0, colon);
                }
                if (!isPrivateOrReserved(cand)) {
                    return cand;
                }
            }
        }
        String[] headers = {"X-Real-IP", "CF-Connecting-IP", "True-Client-IP", "ip"};
        for (String h : headers) {
            String cand = publicIpOrNull(request.getHeader(h));
            if (cand != null) {
                return cand;
            }
        }
        String fwd = request.getHeader("Forwarded");
        if (fwd != null) {
            Matcher m = Pattern.compile("for=\\\"?([\\d\\.]+)").matcher(fwd);
            if (m.find()) {
                String cand = publicIpOrNull(m.group(1));
                if (cand != null) {
                    return cand;
                }
            }
        }
        return request.getRemoteAddr() != null ? request.getRemoteAddr() : "0.0.0.0";
    }


    public IpAddressResp getAddress(HttpServletRequest request){
        String ip = this.extractClientIp(request);
        return this.getProvinceCityByIp(ip);
    }


    /**
     * 从类路径下读取IP地址数据库文件内容并返回字节数组。
     *
     * @return 数据库文件的字节内容
     * @throws RuntimeException 当读取文件出错时抛出运行时异常
     */
    public byte[] getSearchDbContent() {
        try (InputStream dbStream = this.getClass().getClassLoader().getResourceAsStream(DB_PATH)) {
            // 校验文件是否存在
            AssertUtils.notNull(dbStream, ResultCodeEnum.IP_DB_INIT_FAIL);

            // 将文件内容读取为字节数组
            return readInputStreamToByteArray(dbStream);
        } catch (IOException e) {
            throw new RuntimeException("读取地域本地数据库文件失败 ip/ip2region.xdb", e);
        }
    }

    /**
     * 将输入流中的数据读取为字节数组。
     *
     * @param inputStream 输入流
     * @return 字节数组
     * @throws IOException 当读取过程中发生IO异常时抛出
     */
    private byte[] readInputStreamToByteArray(InputStream inputStream) throws IOException {
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                byteArrayOutputStream.write(buffer, 0, bytesRead);
            }
            return byteArrayOutputStream.toByteArray();
        }
    }

    /**
     * 获取公网IP地址，如果是私有地址则返回null
     *
     * @param ip 要处理的IP地址字符串
     * @return 如果是有效公网IP地址则返回该IP，否则返回null
     */
    public static String publicIpOrNull(String ip) {
        // 检查IP地址是否为空或未知
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { return null; }

        // 处理IP地址，去除端口号和引号
        int colon = ip.indexOf(':');
        if (colon > 0) { ip = ip.substring(0, colon); }
        ip = ip.replace("\"", "").trim();

        // 判断是否为私有地址，如果是则返回null
        return isPrivateOrReserved(ip) ? null : ip;
    }

    /**
     * 判断给定的IP地址是否为私有地址或保留地址
     *
     * @param ip 要检查的IP地址字符串
     * @return 如果是私有地址或保留地址返回true，否则返回false
     */
    public static boolean isPrivateOrReserved(String ip) {
        // 检查IP地址是否为空或格式不正确
        if (ip == null) { return true; }
        if (!IPV4.matcher(ip).matches()) { return true; }

        // 解析IP地址的各段数值
        String[] p = ip.split("\\.");
        int a = Integer.parseInt(p[0]), b = Integer.parseInt(p[1]);

        // 检查各种私有地址和保留地址范围
        if (a == 10) { return true; }
        if (a == 172 && b >= 16 && b <= 31) { return true; }
        if (a == 192 && b == 168) { return true; }
        if (a == 127) { return true; }
        if (a == 169 && b == 254) { return true; }
        if (a == 0 || a == 255) { return true; }
        return false;
    }

}
