// kafka — a privacy-respecting metasearch engine
// Copyright (C) 2026-present metamorphosis-dev
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License,// or (at your option) any later version.

package util

import (
	"fmt"
	"net"
	"net/url"
	"strings"
)

// SafeURLScheme validates that a URL is well-formed and uses an acceptable scheme.
// Returns the parsed URL on success, or an error.
func SafeURLScheme(raw string) (*url.URL, error) {
	u, err := url.Parse(raw)
	if err != nil {
		return nil, err
	}
	if u.Scheme != "http" && u.Scheme != "https" {
		return nil, fmt.Errorf("URL must use http or https, got %q", u.Scheme)
	}
	return u, nil
}

// IsPrivateIP returns true if the IP address is in a private, loopback,
// link-local, or otherwise non-routable range.
func IsPrivateIP(host string) bool {
	// Strip port if present.
	h, _, err := net.SplitHostPort(host)
	if err != nil {
		h = host
	}

	// Resolve hostname to IPs.
	ips, err := net.LookupIP(h)
	if err != nil || len(ips) == 0 {
		// If we can't resolve, reject to be safe.
		return true
	}

	for _, ip := range ips {
		if isPrivateIPAddr(ip) {
			return true
		}
	}
	return false
}

func isPrivateIPAddr(ip net.IP) bool {
	privateRanges := []struct {
		network *net.IPNet
	}{
		// Loopback
		{mustParseCIDR("127.0.0.0/8")},
		{mustParseCIDR("::1/128")},
		// RFC 1918
		{mustParseCIDR("10.0.0.0/8")},
		{mustParseCIDR("172.16.0.0/12")},
		{mustParseCIDR("192.168.0.0/16")},
		// RFC 6598 (Carrier-grade NAT)
		{mustParseCIDR("100.64.0.0/10")},
		// Link-local
		{mustParseCIDR("169.254.0.0/16")},
		{mustParseCIDR("fe80::/10")},
		// IPv6 unique local
		{mustParseCIDR("fc00::/7")},
		// IPv4-mapped IPv6 loopback
		{mustParseCIDR("::ffff:127.0.0.0/104")},
	}

	for _, r := range privateRanges {
		if r.network.Contains(ip) {
			return true
		}
	}
	return false
}

func mustParseCIDR(s string) *net.IPNet {
	_, network, err := net.ParseCIDR(s)
	if err != nil {
		panic(fmt.Sprintf("validate: invalid CIDR %q: %v", s, err))
	}
	return network
}

// ValidatePublicURL checks that a URL is well-formed, uses http or https,
// and does not point to a private/reserved IP range.
func ValidatePublicURL(raw string) error {
	u, err := url.Parse(raw)
	if err != nil {
		return fmt.Errorf("invalid URL: %w", err)
	}
	if u.Scheme != "http" && u.Scheme != "https" {
		return fmt.Errorf("URL must use http or https, got %q", u.Scheme)
	}
	if u.Host == "" {
		return fmt.Errorf("URL must have a host")
	}
	if IsPrivateIP(u.Host) {
		return fmt.Errorf("URL points to a private or reserved address: %s", u.Host)
	}
	return nil
}

// SanitizeResultURL ensures a URL is safe for rendering in an href attribute.
// It rejects javascript:, data:, vbscript: and other dangerous schemes.
func SanitizeResultURL(raw string) string {
	if raw == "" {
		return ""
	}
	u, err := url.Parse(raw)
	if err != nil {
		return ""
	}
	switch strings.ToLower(u.Scheme) {
	case "http", "https", "":
		return raw
	default:
		return ""
	}
}
