package sub

import (
	"net"
	"net/netip"
	"strings"
	"sync"

	"github.com/gin-gonic/gin"

	"github.com/mhsanaei/3x-ui/v3/internal/logger"
	"github.com/mhsanaei/3x-ui/v3/internal/web/service"
)

var warnSuppressedForwardedOnce sync.Once

var forwardedHeaderNames = [...]string{"X-Forwarded-Host", "X-Forwarded-Proto", "X-Real-IP"}

func (s *SubService) forwardedHeadersTrusted(c *gin.Context) (trusted bool) {
	if !hasForwardedHeaders(c) {
		return true
	}

	trusted = true
	defer func() {
		_ = recover()
	}()

	configured, err := s.settingService.GetTrustedProxyCIDRs()
	if err != nil {
		return true
	}
	configured = strings.TrimSpace(configured)
	if configured == "" || configured == service.DefaultTrustedProxyCIDRs {
		return true
	}
	return remoteAddrInCIDRs(c.Request.RemoteAddr, configured)
}

func hasForwardedHeaders(c *gin.Context) bool {
	for _, name := range forwardedHeaderNames {
		if c.GetHeader(name) != "" {
			return true
		}
	}
	return false
}

func warnSuppressedForwardedHeaders(c *gin.Context) {
	present := make([]string, 0, 3)
	for _, name := range forwardedHeaderNames {
		if c.GetHeader(name) != "" {
			present = append(present, name)
		}
	}
	if len(present) == 0 {
		return
	}
	headers := strings.Join(present, ", ")
	logger.Debugf("sub: ignoring %s from %s, which is outside trustedProxyCIDRs", headers, c.Request.RemoteAddr)
	warnSuppressedForwardedOnce.Do(func() {
		logger.Warningf("sub: ignoring %s from %s because it is outside trustedProxyCIDRs; subscription URLs will use the request host. Add the proxy to that setting, or set subURI, if the generated links look wrong.", headers, c.Request.RemoteAddr)
	})
}

func remoteAddrInCIDRs(remoteAddr, cidrs string) bool {
	host := remoteAddr
	if h, _, err := net.SplitHostPort(remoteAddr); err == nil {
		host = h
	}
	addr, err := netip.ParseAddr(strings.TrimSpace(host))
	if err != nil {
		return false
	}
	addr = addr.Unmap()
	for value := range strings.SplitSeq(cidrs, ",") {
		value = strings.TrimSpace(value)
		if value == "" {
			continue
		}
		if prefix, err := netip.ParsePrefix(value); err == nil {
			if prefix.Contains(addr) {
				return true
			}
			continue
		}
		if proxyIP, err := netip.ParseAddr(value); err == nil && proxyIP.Unmap() == addr.Unmap() {
			return true
		}
	}
	return false
}
