package shop.alien.store.util;

import java.net.IDN;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 短链目标地址校验：HTTPS（可配置是否允许 HTTP）、长度、内网/本地地址、可选域名白名单。
 */
public final class ShortLinkUrlValidator {

    private static final int MAX_URL_LENGTH = 32768;

    private ShortLinkUrlValidator() {
    }

    /**
     * @param longUrl                   原始 URL
     * @param allowedHosts              允许的 host（小写），如 {@code www.example.com}；为空表示不限制 host（仍会拦截内网地址，除非 {@code allowLocalNetworkTarget})
     * @param httpsOnly                 true 时仅允许 https
     * @param allowLocalNetworkTarget   true 时不拦截 localhost/内网 IP（仅用于本地开发，禁止线上开启）
     * @return 规范化后的 URL（trim）
     */
    public static String validateAndNormalize(String longUrl, Set<String> allowedHosts, boolean httpsOnly,
                                              boolean allowLocalNetworkTarget) {
        if (longUrl == null || longUrl.trim().isEmpty()) {
            throw new IllegalArgumentException("长链接不能为空");
        }
        String trimmed = longUrl.trim();
        if (trimmed.length() > MAX_URL_LENGTH) {
            throw new IllegalArgumentException("长链接过长");
        }
        URI uri;
        try {
            uri = new URI(trimmed);
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException("长链接格式无效");
        }
        String scheme = uri.getScheme();
        if (scheme == null) {
            throw new IllegalArgumentException("长链接需包含协议（https）");
        }
        String s = scheme.toLowerCase(Locale.ROOT);
        if (httpsOnly) {
            if (!"https".equals(s)) {
                throw new IllegalArgumentException("仅支持 https 长链接");
            }
        } else {
            if (!"https".equals(s) && !"http".equals(s)) {
                throw new IllegalArgumentException("仅支持 http / https 长链接");
            }
        }
        String host = uri.getHost();
        if (host == null || host.isEmpty()) {
            throw new IllegalArgumentException("长链接缺少主机名");
        }
        String hostLower = IDN.toUnicode(host).toLowerCase(Locale.ROOT);
        if (!allowLocalNetworkTarget && isBlockedHost(hostLower)) {
            throw new IllegalArgumentException("不允许使用该主机地址");
        }
        if (allowedHosts != null && !allowedHosts.isEmpty()) {
            if (!hostMatchesAllowlist(hostLower, allowedHosts)) {
                throw new IllegalArgumentException("长链接域名不在允许列表内");
            }
        }
        return trimmed;
    }

    public static Set<String> parseAllowedHostsCsv(String csv) {
        if (csv == null || csv.trim().isEmpty()) {
            return Collections.emptySet();
        }
        return Arrays.stream(csv.split(","))
                .map(String::trim)
                .filter(s -> !s.isEmpty())
                .map(s -> s.toLowerCase(Locale.ROOT))
                .collect(Collectors.toCollection(LinkedHashSet::new));
    }

    private static boolean hostMatchesAllowlist(String host, Set<String> allowedHosts) {
        for (String allowed : allowedHosts) {
            if (allowed == null || allowed.isEmpty()) {
                continue;
            }
            if (host.equals(allowed) || host.endsWith("." + allowed)) {
                return true;
            }
        }
        return false;
    }

    private static boolean isBlockedHost(String hostLower) {
        if ("localhost".equals(hostLower)) {
            return true;
        }
        if ("0.0.0.0".equals(hostLower) || "::1".equals(hostLower)) {
            return true;
        }
        // IPv4 简单规则：127.*、10.*、192.168.*、169.254.*、172.16-31.*
        if (hostLower.startsWith("127.")) {
            return true;
        }
        if (hostLower.startsWith("10.")) {
            return true;
        }
        if (hostLower.startsWith("192.168.")) {
            return true;
        }
        if (hostLower.startsWith("169.254.")) {
            return true;
        }
        if (hostLower.startsWith("172.")) {
            String[] parts = hostLower.split("\\.");
            if (parts.length >= 2) {
                try {
                    int second = Integer.parseInt(parts[1]);
                    if (second >= 16 && second <= 31) {
                        return true;
                    }
                } catch (NumberFormatException ignored) {
                    // ignore
                }
            }
        }
        return false;
    }
}
