package com.cameleer.server.app.outbound;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;

/**
 * Validates outbound webhook URLs against SSRF pitfalls: rejects hosts that resolve to
 * loopback, link-local, or RFC-1918 private ranges (and IPv6 equivalents).
 *
 * Per spec §17. The `cameleer.server.outbound-http.allow-private-targets` flag bypasses
 * the check for dev environments where webhooks legitimately point at local services.
 */
@Component
public class SsrfGuard {

    private final boolean allowPrivate;

    public SsrfGuard(
        @Value("${cameleer.server.outbound-http.allow-private-targets:false}") boolean allowPrivate
    ) {
        this.allowPrivate = allowPrivate;
    }

    public void validate(URI uri) {
        if (allowPrivate) return;
        String host = uri.getHost();
        if (host == null || host.isBlank()) {
            throw new IllegalArgumentException("URL must include a host: " + uri);
        }
        if ("localhost".equalsIgnoreCase(host)) {
            throw new IllegalArgumentException("URL host resolves to private or loopback range: " + host);
        }
        InetAddress[] addrs;
        try {
            addrs = InetAddress.getAllByName(host);
        } catch (UnknownHostException e) {
            throw new IllegalArgumentException("URL host does not resolve: " + host, e);
        }
        for (InetAddress addr : addrs) {
            if (isPrivate(addr)) {
                throw new IllegalArgumentException("URL host resolves to private or loopback range: " + host + " -> " + addr.getHostAddress());
            }
        }
    }

    private static boolean isPrivate(InetAddress addr) {
        if (addr.isLoopbackAddress()) return true;
        if (addr.isLinkLocalAddress()) return true;
        if (addr.isSiteLocalAddress()) return true;     // 10/8, 172.16/12, 192.168/16
        if (addr.isAnyLocalAddress())  return true;     // 0.0.0.0, ::
        if (addr instanceof Inet6Address ip6) {
            byte[] raw = ip6.getAddress();
            // fc00::/7 unique-local
            if ((raw[0] & 0xfe) == 0xfc) return true;
        }
        if (addr instanceof Inet4Address ip4) {
            byte[] raw = ip4.getAddress();
            // 169.254.0.0/16 link-local (also matches isLinkLocalAddress but doubled-up for safety)
            if ((raw[0] & 0xff) == 169 && (raw[1] & 0xff) == 254) return true;
        }
        return false;
    }
}
