package executionpool

import (
	"context"
	"crypto/tls"
	"net/http"
	"net/http/httptrace"
	"time"
)

type ProbeTarget struct {
	RouteProfile RouteProfile
	URL          string
	Method       string
	Timeout      time.Duration
}

type ProbeResult struct {
	Reachable   bool
	StatusCode  int
	DNSDuration time.Duration
	TCPDuration time.Duration
	TLSDuration time.Duration
	FirstByte   time.Duration
	SampledAt   time.Time
	ErrorClass  string
}

type NetworkProbe interface {
	Probe(context.Context, ProbeTarget) ProbeResult
}

type HTTPProbe struct {
	Client *http.Client
	Now    func() time.Time
}

func (p HTTPProbe) Probe(ctx context.Context, target ProbeTarget) ProbeResult {
	result := ProbeResult{}
	now := time.Now
	if p.Now != nil {
		now = p.Now
	}
	result.SampledAt = now()
	method := target.Method
	if method == "" {
		method = http.MethodHead
	}
	timeout := target.Timeout
	if timeout <= 0 {
		timeout = 3 * time.Second
	}
	probeCtx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()
	var dnsStart, tcpStart, tlsStart time.Time
	requestStart := now()
	trace := &httptrace.ClientTrace{
		DNSStart: func(httptrace.DNSStartInfo) { dnsStart = now() },
		DNSDone: func(httptrace.DNSDoneInfo) {
			if !dnsStart.IsZero() {
				result.DNSDuration = now().Sub(dnsStart)
			}
		},
		ConnectStart: func(_, _ string) { tcpStart = now() },
		ConnectDone: func(_, _ string, _ error) {
			if !tcpStart.IsZero() {
				result.TCPDuration = now().Sub(tcpStart)
			}
		},
		TLSHandshakeStart: func() { tlsStart = now() },
		TLSHandshakeDone: func(tls.ConnectionState, error) {
			if !tlsStart.IsZero() {
				result.TLSDuration = now().Sub(tlsStart)
			}
		},
		GotFirstResponseByte: func() { result.FirstByte = now().Sub(requestStart) },
	}
	request, err := http.NewRequestWithContext(httptrace.WithClientTrace(probeCtx, trace), method, target.URL, nil)
	if err != nil {
		result.ErrorClass = "invalid_target"
		return result
	}
	client := p.Client
	if client == nil {
		client = http.DefaultClient
	}
	response, err := client.Do(request)
	if err != nil {
		if probeCtx.Err() != nil {
			result.ErrorClass = "timeout"
		} else {
			result.ErrorClass = "network"
		}
		return result
	}
	defer response.Body.Close()
	result.StatusCode = response.StatusCode
	// Any HTTP response, including 401/403/404/405, proves transport reachability.
	result.Reachable = true
	return result
}
