package tech.easyflow.skill.gitimport;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import tech.easyflow.common.web.exceptions.BusinessException;

import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.net.URL;
import java.util.Arrays;
import java.util.Locale;

/**
 * Git 仓库远程地址访问策略，负责协议、凭证、端口与 SSRF 边界校验。
 */
@Component
public class GitRepositoryAccessPolicy {

    private final SkillGitImportProperties properties;
    private final HostResolver hostResolver;

    /**
     * 创建 Git 地址访问策略。
     *
     * @param properties Git 导入配置
     */
    @Autowired
    public GitRepositoryAccessPolicy(SkillGitImportProperties properties) {
        this(properties, InetAddress::getAllByName);
    }

    /**
     * 创建可替换主机解析器的 Git 地址访问策略。
     *
     * @param properties Git 导入配置
     * @param hostResolver 主机解析器
     */
    GitRepositoryAccessPolicy(SkillGitImportProperties properties, HostResolver hostResolver) {
        this.properties = properties;
        this.hostResolver = hostResolver;
    }

    /**
     * 校验并规范化用户输入的 Git 仓库地址。
     *
     * @param repositoryUrl 用户输入地址
     * @return 规范化 HTTPS 地址
     * @throws BusinessException 地址不符合安全边界
     */
    public String normalizeRepositoryUrl(String repositoryUrl) {
        if (repositoryUrl == null || repositoryUrl.isBlank()) {
            throw new BusinessException("请输入 Git 仓库地址");
        }
        URI uri;
        try {
            uri = new URI(repositoryUrl.trim()).normalize();
        } catch (URISyntaxException exception) {
            throw new BusinessException("Git 仓库地址格式不正确");
        }
        validateUri(uri, true);
        String path = uri.getRawPath();
        if (path == null || path.isBlank() || "/".equals(path)) {
            throw new BusinessException("Git 仓库地址缺少仓库路径");
        }
        String normalizedPath = path.length() > 1 && path.endsWith("/")
                ? path.substring(0, path.length() - 1) : path;
        try {
            return new URI("https", null, uri.getHost().toLowerCase(Locale.ROOT),
                    uri.getPort(), normalizedPath, null, null).toASCIIString();
        } catch (URISyntaxException exception) {
            throw new BusinessException("Git 仓库地址格式不正确");
        }
    }

    /**
     * 校验 JGit 实际发起的连接地址，包括重定向后的服务地址。
     *
     * @param url 连接地址
     * @throws java.io.IOException 地址被访问策略拒绝
     */
    ResolvedConnection resolveConnection(URL url) throws IOException {
        try {
            URI uri = url.toURI();
            InetAddress[] addresses = validateUri(uri, false);
            return new ResolvedConnection(uri.getHost().toLowerCase(Locale.ROOT), addresses);
        } catch (URISyntaxException | BusinessException exception) {
            throw new IOException(exception.getMessage(), exception);
        }
    }

    private InetAddress[] validateUri(URI uri, boolean repositoryInput) {
        if (!"https".equalsIgnoreCase(uri.getScheme())) {
            throw new BusinessException("Git 仓库仅支持 HTTPS 地址");
        }
        if (uri.getUserInfo() != null) {
            throw new BusinessException("Git 仓库地址不能包含用户名或密码");
        }
        if (repositoryInput && (uri.getQuery() != null || uri.getFragment() != null)) {
            throw new BusinessException("Git 仓库地址不能包含查询参数或片段");
        }
        String host = uri.getHost();
        if (host == null || host.isBlank()) {
            throw new BusinessException("Git 仓库地址缺少有效主机");
        }
        int port = uri.getPort() < 0 ? 443 : uri.getPort();
        if (!properties.getAllowedPorts().contains(port)) {
            throw new BusinessException("Git 仓库端口不在允许范围内");
        }
        String normalizedHost = host.toLowerCase(Locale.ROOT);
        InetAddress[] addresses;
        try {
            addresses = hostResolver.resolve(normalizedHost);
        } catch (UnknownHostException exception) {
            throw new BusinessException("无法解析 Git 仓库主机");
        }
        if (addresses.length == 0) {
            throw new BusinessException("无法解析 Git 仓库主机");
        }
        if (!properties.getTrustedPrivateHosts().contains(normalizedHost)
                && Arrays.stream(addresses).anyMatch(this::isBlockedAddress)) {
            throw new BusinessException("Git 仓库地址指向受限网络");
        }
        return addresses.clone();
    }

    private boolean isBlockedAddress(InetAddress address) {
        if (address.isAnyLocalAddress() || address.isLoopbackAddress() || address.isLinkLocalAddress()
                || address.isSiteLocalAddress() || address.isMulticastAddress()) {
            return true;
        }
        byte[] bytes = address.getAddress();
        if (address instanceof Inet4Address && bytes.length == 4) {
            int first = Byte.toUnsignedInt(bytes[0]);
            int second = Byte.toUnsignedInt(bytes[1]);
            int third = Byte.toUnsignedInt(bytes[2]);
            return first == 0 || first == 10 || first == 127 || first >= 224
                    || (first == 100 && second >= 64 && second <= 127)
                    || (first == 169 && second == 254)
                    || (first == 172 && second >= 16 && second <= 31)
                    || (first == 192 && second == 0 && (third == 0 || third == 2))
                    || (first == 192 && second == 88 && third == 99)
                    || (first == 192 && second == 168)
                    || (first == 198 && (second == 18 || second == 19))
                    || (first == 198 && second == 51 && third == 100)
                    || (first == 203 && second == 0 && third == 113);
        }
        if (address instanceof Inet6Address && bytes.length == 16) {
            int first = Byte.toUnsignedInt(bytes[0]);
            int second = Byte.toUnsignedInt(bytes[1]);
            return (first & 0xFE) == 0xFC || (first == 0xFE && (second & 0xC0) == 0x80)
                    || (first == 0x20 && second == 0x01 && Byte.toUnsignedInt(bytes[2]) == 0x0D
                    && Byte.toUnsignedInt(bytes[3]) == 0xB8);
        }
        return false;
    }

    @FunctionalInterface
    interface HostResolver {

        /**
         * 解析目标主机的全部地址。
         *
         * @param host 规范化主机名
         * @return 解析地址
         * @throws UnknownHostException 主机无法解析
         */
        InetAddress[] resolve(String host) throws UnknownHostException;
    }

    record ResolvedConnection(String host, InetAddress[] addresses) {

        ResolvedConnection {
            addresses = addresses.clone();
        }

        /**
         * 获取用于实际连接的固定地址。
         *
         * @return 地址副本
         */
        @Override
        public InetAddress[] addresses() {
            return addresses.clone();
        }
    }
}
