package runner

import (
	"context"
	"crypto/tls"
	"net/http"
	"net/http/httptrace"
	"sort"
	"sync"
	"time"

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/executionpool"
	"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)

type passiveRouteObserver struct {
	mu              sync.Mutex
	samples         int
	successes       int
	connectTLS      []time.Duration
	uploadBytesRate []float64
}

type passiveRouteRoundTripper struct {
	base     http.RoundTripper
	observer *passiveRouteObserver
}

func (observer *passiveRouteObserver) wrap(client *http.Client) *http.Client {
	if client == nil {
		client = http.DefaultClient
	}
	cloned := *client
	base := cloned.Transport
	if base == nil {
		base = http.DefaultTransport
	}
	cloned.Transport = passiveRouteRoundTripper{base: base, observer: observer}
	return &cloned
}

func (transport passiveRouteRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
	startedAt := time.Now()
	var connectStartedAt time.Time
	var connectDuration time.Duration
	var tlsStartedAt time.Time
	var tlsDuration time.Duration
	var wroteRequestAt time.Time
	trace := &httptrace.ClientTrace{
		ConnectStart: func(_, _ string) { connectStartedAt = time.Now() },
		ConnectDone: func(_, _ string, _ error) {
			if !connectStartedAt.IsZero() {
				connectDuration = time.Since(connectStartedAt)
			}
		},
		TLSHandshakeStart: func() { tlsStartedAt = time.Now() },
		TLSHandshakeDone: func(tls.ConnectionState, error) {
			if !tlsStartedAt.IsZero() {
				tlsDuration = time.Since(tlsStartedAt)
			}
		},
		WroteRequest: func(httptrace.WroteRequestInfo) { wroteRequestAt = time.Now() },
	}
	traced := request.Clone(httptrace.WithClientTrace(request.Context(), trace))
	response, err := transport.base.RoundTrip(traced)
	transport.observer.record(
		err == nil,
		connectDuration+tlsDuration,
		request.ContentLength,
		startedAt,
		wroteRequestAt,
	)
	return response, err
}

func (observer *passiveRouteObserver) record(success bool, connectTLS time.Duration, contentLength int64, startedAt, wroteRequestAt time.Time) {
	observer.mu.Lock()
	defer observer.mu.Unlock()
	observer.samples++
	if success {
		observer.successes++
	}
	if connectTLS > 0 {
		observer.connectTLS = append(observer.connectTLS, connectTLS)
	}
	if contentLength > 0 && !wroteRequestAt.IsZero() && wroteRequestAt.After(startedAt) {
		observer.uploadBytesRate = append(observer.uploadBytesRate, float64(contentLength)/wroteRequestAt.Sub(startedAt).Seconds())
	}
}

func (observer *passiveRouteObserver) snapshot(poolID, routeProfileKey string) executionpool.RouteObservation {
	observer.mu.Lock()
	defer observer.mu.Unlock()
	return executionpool.RouteObservation{
		PoolID: poolID, RouteProfileKey: routeProfileKey,
		SampleCount: observer.samples, SuccessCount: observer.successes,
		ConnectTLSP95:        durationP95(observer.connectTLS),
		UploadBytesPerSecond: floatP95(observer.uploadBytesRate),
	}
}

func durationP95(values []time.Duration) time.Duration {
	if len(values) == 0 {
		return 0
	}
	copyOfValues := append([]time.Duration(nil), values...)
	sort.Slice(copyOfValues, func(i, j int) bool { return copyOfValues[i] < copyOfValues[j] })
	return copyOfValues[(len(copyOfValues)*95+99)/100-1]
}

func floatP95(values []float64) float64 {
	if len(values) == 0 {
		return 0
	}
	copyOfValues := append([]float64(nil), values...)
	sort.Float64s(copyOfValues)
	return copyOfValues[(len(copyOfValues)*95+99)/100-1]
}

func (s *Service) recordPassiveRouteObservation(task store.GatewayTask, observer *passiveRouteObserver) {
	if observer == nil || !s.routingEnabled() || task.RouteProfileKey == "" {
		return
	}
	poolID := task.AssignedPoolID
	if poolID == "" && s.cfg.RunsAsyncExecutionWorker() {
		poolID = s.cfg.ExecutionPoolID
	}
	observation := observer.snapshot(poolID, task.RouteProfileKey)
	if observation.PoolID == "" || observation.SampleCount == 0 {
		return
	}
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()
	if err := s.coordinationStore.RecordRouteObservation(ctx, observation); err != nil && s.logger != nil {
		s.logger.Warn("record passive route observation failed", "error_category", "route_observation_failed")
	}
}
