package node

import (
	"context"
	"strconv"
	"sync"
	"time"

	runtime "iop/packages/go/execution"
)

// healthObservationSequencer allocates connection-scoped, monotonically
// increasing health-observation sequence values. Only a live bound transport
// Session provides one; internal or unbound execution paths pass nil so the
// terminal omits health_observation_seq and never invents a process-global
// generation.
type healthObservationSequencer interface {
	NextHealthObservationSeq() uint64
}

func contextStillActive(ctx context.Context) bool { return ctx == nil || ctx.Err() == nil }

// stallObservation is the bounded evidence joined after the watchdog claims a
// stall. It carries only Node-owned values; probe evidence is observation only
// and never changes the fence, resets progress, or authorizes retry.
type stallObservation struct {
	fence  string
	idle   time.Duration
	health HealthProbeEvidence
	seq    uint64
	hasSeq bool
}

func stallObservationFrom(result attemptResult, idle time.Duration, seq healthObservationSequencer) stallObservation {
	obs := stallObservation{fence: result.fence, idle: idle, health: result.health}
	if seq != nil {
		obs.seq = seq.NextHealthObservationSeq()
		obs.hasSeq = true
	}
	return obs
}

// stallMetadata builds the single allowlisted stall-terminal metadata map.
func stallMetadata(runID, adapter, target string, obs stallObservation) map[string]string {
	classification := obs.health.Health
	if classification == "" {
		classification = runtime.HealthUnknown
	}
	providerStatus := runtime.ProviderStatusUnknown
	switch classification {
	case runtime.RequestStalled:
		providerStatus = runtime.ProviderStatusAvailable
	case runtime.ProviderUnhealthy:
		providerStatus = runtime.ProviderStatusUnavailable
	}
	metadata := map[string]string{
		"failure_code":            string(runtime.FailureCodeResponseStalled),
		"provider_health":         string(providerStatus),
		"liveness_classification": string(classification),
		"idle_duration_ms":        strconv.FormatInt(obs.idle.Milliseconds(), 10),
		"run_id":                  runID,
		"attempt_id":              runID,
		"attempt_fence":           obs.fence,
		"adapter":                 adapter,
		"target":                  target,
	}
	if obs.hasSeq {
		metadata["health_observation_seq"] = strconv.FormatUint(obs.seq, 10)
	}
	return metadata
}

type healthProbe func() HealthProbeEvidence

func healthProbeFor(adapter runtime.Provider, adapterName, instanceKey, target string) healthProbe {
	resolved := ResolveProbeFunc(adapter)
	return func() HealthProbeEvidence {
		return ProbeHealth(adapterName, instanceKey, target, resolved)
	}
}

func runHealthProbe(probe healthProbe) HealthProbeEvidence {
	if probe == nil {
		return HealthProbeEvidence{Health: runtime.HealthUnknown, Status: runtime.ProviderStatusUnknown}
	}
	return probe()
}

type attemptResult struct {
	providerErr      error
	stalled          bool
	providerReturned bool
	fence            string
	health           HealthProbeEvidence
}

type attemptCleanup struct {
	once sync.Once
	fn   func()
}

func newAttemptCleanup(fn func()) *attemptCleanup { return &attemptCleanup{fn: fn} }

func (c *attemptCleanup) run() {
	if c != nil {
		c.once.Do(c.fn)
	}
}

func (c *attemptCleanup) afterProviderReturn(providerDone <-chan error) {
	go func() {
		<-providerDone
		c.run()
	}()
}

func startProviderAttempt(execute func() error) <-chan error {
	done := make(chan error, 1)
	go func() { done <- execute() }()
	return done
}

// joinStallEvidence starts the fixed close-grace fence and exact-target probe
// together, then waits for both bounded outcomes without serial extension.
func joinStallEvidence(clock attemptClock, providerDone <-chan error, probe healthProbe) attemptResult {
	probeDone := make(chan HealthProbeEvidence, 1)
	go func() { probeDone <- runHealthProbe(probe) }()

	grace := clock.NewTimer(defaultAttemptCloseGrace)
	result := attemptResult{stalled: true, fence: "unconfirmed"}
	select {
	case result.providerErr = <-providerDone:
		result.providerReturned = true
		result.fence = "confirmed"
	case <-grace.C():
	}
	grace.Stop()
	result.health = <-probeDone
	return result
}
