package webhook

import (
	"net"
	"net/url"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// reservedCIDRs lists IP ranges that must never be targeted by outbound webhook requests.
// Covers loopback, RFC-1918 private, link-local (including cloud IMDS at 169.254.169.254),
// and their IPv6 equivalents.
var reservedCIDRs = []string{
	"127.0.0.0/8",    // IPv4 loopback
	"10.0.0.0/8",     // RFC-1918 class A
	"172.16.0.0/12",  // RFC-1918 class B
	"192.168.0.0/16", // RFC-1918 class C
	"169.254.0.0/16", // Link-local / cloud IMDS
	"::1/128",        // IPv6 loopback
	"fc00::/7",       // IPv6 unique local
	"fe80::/10",      // IPv6 link-local
}

// reservedNetworks is the parsed form of reservedCIDRs, built once at startup.
var reservedNetworks []*net.IPNet

func init() {
	for _, cidr := range reservedCIDRs {
		_, network, err := net.ParseCIDR(cidr)
		if err != nil {
			panic("webhook: invalid reserved CIDR " + cidr + ": " + err.Error())
		}
		reservedNetworks = append(reservedNetworks, network)
	}
}

// AllowPrivateIPs controls whether webhook URLs may resolve to reserved/private
// IP addresses. When true, the SSRF protection is disabled. This is useful for
// self-hosted deployments where webhooks target services on the local network.
var AllowPrivateIPs bool

// isReservedIP reports whether ip falls within any reserved/private range.
func isReservedIP(ip net.IP) bool {
	if AllowPrivateIPs {
		return false
	}
	for _, network := range reservedNetworks {
		if network.Contains(ip) {
			return true
		}
	}
	return false
}

// ValidateURL checks that rawURL:
//  1. Parses as a valid absolute URL.
//  2. Uses the http or https scheme.
//  3. Does not resolve to a reserved/private IP address.
//
// It returns a gRPC InvalidArgument status error so callers can return it directly.
func ValidateURL(rawURL string) error {
	u, err := url.ParseRequestURI(rawURL)
	if err != nil {
		return status.Errorf(codes.InvalidArgument, "invalid webhook URL: %v", err)
	}
	if u.Scheme != "http" && u.Scheme != "https" {
		return status.Errorf(codes.InvalidArgument, "webhook URL must use http or https scheme, got %q", u.Scheme)
	}

	ips, err := net.LookupHost(u.Hostname())
	if err != nil {
		return status.Errorf(codes.InvalidArgument, "webhook URL hostname could not be resolved: %v", err)
	}

	for _, ipStr := range ips {
		ip := net.ParseIP(ipStr)
		if ip != nil && isReservedIP(ip) {
			return status.Errorf(codes.InvalidArgument, "webhook URL must not resolve to a reserved or private IP address")
		}
	}
	return nil
}
