package netreport

import (
	
	
	
	
	
	
	

	
)

// runHTTPSProbe fetches the relay's probe path ("/ping") and times the round
// trip. It follows no redirects, matching run_https_probe
// (iroh/src/net_report/reportgen.rs:817). A non-2xx response is an error.
//
// tlsConfig, if non-nil, overrides TLS verification (used in tests with
// self-signed certs).
func runHTTPSProbe( context.Context,  netaddr.RelayURL,  *tls.Config) (*probeReport, error) {
	,  := joinPath(, relayProbePath)
	if  != nil {
		return nil, 
	}
	 := newProbeClient()

	,  := http.NewRequestWithContext(, http.MethodGet, , nil)
	if  != nil {
		return nil, fmt.Errorf("build request: %w", )
	}

	 := time.Now()
	,  := .Do()
	if  != nil {
		return nil, fmt.Errorf("https request: %w", )
	}
	defer .Body.Close()
	 := time.Since()

	if .StatusCode < 200 || .StatusCode >= 300 {
		return nil, fmt.Errorf("https probe: unexpected status %d", .StatusCode)
	}
	// Drain the body (up to 8 KiB) to be polite to the server.
	drain(.Body, 8<<10)

	return &probeReport{probe: ProbeHTTPS, relay: , latency: }, nil
}

// checkCaptivePortal reports whether a captive portal is intercepting HTTP. It
// fetches "/generate_204" with an X-Iroh-Challenge header and requires both a
// 204 status and a matching X-Iroh-Response echo; otherwise a captive portal is
// assumed. It follows no redirects. Mirrors check_captive_portal
// (iroh/src/net_report/reportgen.rs:614).
func checkCaptivePortal( context.Context,  netaddr.RelayURL,  *tls.Config) (bool, error) {
	 := .Host()
	if  == "" {
		return false, fmt.Errorf("captive portal: %w", errMissingHost)
	}
	// The challenge is keyed on the bare hostname (no port), matching
	// reportgen.rs:614 (url.host_str()). The request, however, targets the
	// relay's actual host:port so a relay reachable on a non-default port (as
	// in tests) is honored; production relays listen on port 80 for this check.
	 := "ts_" + 
	 := 
	if  := .URL();  != nil && .Host != "" {
		 = .Host
	}
	 := "http://" +  + captivePortalPath

	 := newProbeClient()
	,  := http.NewRequestWithContext(, http.MethodGet, , nil)
	if  != nil {
		return false, fmt.Errorf("build request: %w", )
	}
	.Header.Set(challengeHeader, )

	,  := .Do()
	if  != nil {
		return false, fmt.Errorf("captive portal request: %w", )
	}
	defer .Body.Close()
	drain(.Body, 8<<10)

	 := "response " + 
	 := .Header.Get(responseHeader) == 
	 := .StatusCode != http.StatusNoContent || !
	return , nil
}

// errMissingHost is returned when a relay URL has no host component.
var errMissingHost = fmt.Errorf("relay url has no host")

// joinPath resolves path against the relay URL, mirroring RelayURL::join.
func joinPath( netaddr.RelayURL,  string) (string, error) {
	 := .URL()
	if  == nil {
		return "", fmt.Errorf("join: %w", errMissingHost)
	}
	,  := url.Parse()
	if  != nil {
		return "", fmt.Errorf("join: %w", )
	}
	return .ResolveReference().String(), nil
}

// newProbeClient builds an HTTP client that never follows redirects, mirroring
// the reqwest builders in reportgen.rs (redirect::Policy::none).
func newProbeClient( *tls.Config) *http.Client {
	 := &http.Transport{}
	if  != nil {
		.TLSClientConfig = 
	}
	return &http.Client{
		Transport: ,
		CheckRedirect: func(*http.Request, []*http.Request) error {
			return http.ErrUseLastResponse
		},
	}
}

// drain reads and discards up to limit bytes from r.
func drain( interface{ ([]byte) (int, error) },  int) {
	 := make([]byte, 4096)
	 := 0
	for  <  {
		,  := .()
		 += 
		if  != nil {
			return
		}
	}
}

// lookupIPStaggered resolves host to IP addresses using the staggered retry
// schedule (dnsStaggerMs). Each delay starts a fresh lookup; the first to
// return wins and cancels the rest. It mirrors
// DnsResolver::lookup_ipv4_ipv6_staggered (iroh/src/address_lookup/dns.rs:22),
// bounded by dnsTimeout.
//
// resolver, if nil, defaults to net.DefaultResolver.
func lookupIPStaggered( context.Context,  *net.Resolver,  string) ([]net.IPAddr, error) {
	if  == nil {
		 = net.DefaultResolver
	}
	,  := context.WithTimeout(, dnsTimeout)
	defer ()

	type  struct {
		 []net.IPAddr
		   error
	}
	 := make(chan , len(dnsStaggerMs)+1)

	// delays is the staggered schedule plus an immediate first attempt at 0ms,
	// matching how the Rust resolver fires the first call before the first
	// stagger delay.
	 := append([]int{0}, dnsStaggerMs...)
	for ,  := range  {
		go func( time.Duration) {
			if  > 0 {
				select {
				case <-time.After():
				case <-.Done():
					return
				}
			}
			,  := .LookupIPAddr(, )
			select {
			case  <- {, }:
			case <-.Done():
			}
		}(time.Duration() * time.Millisecond)
	}

	var  error
	for range  {
		select {
		case  := <-:
			if . == nil && len(.) > 0 {
				return ., nil
			}
			if . != nil {
				 = .
			}
		case <-.Done():
			if  != nil {
				return nil, 
			}
			return nil, .Err()
		}
	}
	if  != nil {
		return nil, 
	}
	return nil, fmt.Errorf("no addresses for %s", )
}