// Package testutil hosts the synthetic agents used to exercise the harness
// without LLMs. The nullagent reads its inbox, optionally calls a scripted
// Step function, and acks. The chaos test drives the harness through random
// kills using nullagents to prove the transport + reaper invariants.
package testutil

import (
	"context"
	"sync/atomic"
	"time"

	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/envelope"
	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/transport"
)

// Step is the synthetic agent's per-message hook. It decides what to do with
// each received envelope:
//   - reply: a single reply envelope (often a Report or Answer); nil = no reply
//   - ack: true means Ack the inbound; false means Nack (forces requeue/fail)
//   - die: true means stop the agent loop immediately after handling
type Step func(in *envelope.Envelope) (reply *envelope.Envelope, ack bool, die bool)

// NullAgent runs a Receive→Step→Ack/Nack loop until ctx is canceled or Step
// returns die=true.
type NullAgent struct {
	ID    string
	Queue *transport.Queue
	Step  Step

	// Stats
	Received atomic.Int64
	Acked    atomic.Int64
	Nacked   atomic.Int64
	Replied  atomic.Int64

	// PollInterval controls idle backoff when the inbox is empty.
	PollInterval time.Duration
}

// Run blocks until ctx is canceled or Step says die.
func (a *NullAgent) Run(ctx context.Context) {
	if a.PollInterval == 0 {
		a.PollInterval = 5 * time.Millisecond
	}
	for {
		select {
		case <-ctx.Done():
			return
		default:
		}
		e, err := a.Queue.Receive(ctx, a.ID)
		if err != nil {
			return
		}
		if e == nil {
			select {
			case <-ctx.Done():
				return
			case <-time.After(a.PollInterval):
				continue
			}
		}
		a.Received.Add(1)

		var reply *envelope.Envelope
		ack := true
		die := false
		if a.Step != nil {
			reply, ack, die = a.Step(e)
		}

		if ack {
			_ = a.Queue.Ack(ctx, e.ID)
			a.Acked.Add(1)
		} else {
			_ = a.Queue.Nack(ctx, e.ID, "nullagent-nack")
			a.Nacked.Add(1)
		}
		if reply != nil {
			if err := a.Queue.Send(ctx, reply); err == nil {
				a.Replied.Add(1)
			}
		}
		if die {
			return
		}
	}
}
