package openai_compat

import (
	"context"
	"errors"
	"sync"
	"testing"

	runtime "iop/packages/go/execution"
)

type fakeSink struct {
	mu     sync.Mutex
	events []runtime.RuntimeEvent
}

func (s *fakeSink) Emit(_ context.Context, event runtime.RuntimeEvent) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.events = append(s.events, event)
	return nil
}

func (s *fakeSink) all() []runtime.RuntimeEvent {
	s.mu.Lock()
	defer s.mu.Unlock()
	return append([]runtime.RuntimeEvent(nil), s.events...)
}

type fakeTunnelSink struct {
	mu     sync.Mutex
	frames []runtime.ProviderTunnelFrame
}

func (s *fakeTunnelSink) EmitTunnelFrame(_ context.Context, frame runtime.ProviderTunnelFrame) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.frames = append(s.frames, frame)
	return nil
}

func (s *fakeTunnelSink) all() []runtime.ProviderTunnelFrame {
	s.mu.Lock()
	defer s.mu.Unlock()
	return append([]runtime.ProviderTunnelFrame(nil), s.frames...)
}

// failingTunnelSink emulates the Edge socket write failing after failAfter
// successful frames, as when the Edge relay aborts an abandoned tunnel.
type failingTunnelSink struct {
	mu        sync.Mutex
	frames    []runtime.ProviderTunnelFrame
	failAfter int
}

func (s *failingTunnelSink) EmitTunnelFrame(_ context.Context, frame runtime.ProviderTunnelFrame) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if len(s.frames) >= s.failAfter {
		return errors.New("edge socket write failed")
	}
	s.frames = append(s.frames, frame)
	return nil
}

func (s *failingTunnelSink) all() []runtime.ProviderTunnelFrame {
	s.mu.Lock()
	defer s.mu.Unlock()
	return append([]runtime.ProviderTunnelFrame(nil), s.frames...)
}

// assertOrderedTunnelFrames verifies S09 frame semantics: contiguous sequences
// starting at 0 (no drop/reorder) and exactly one terminal END/ERROR frame,
// which must be the last frame emitted.
func assertOrderedTunnelFrames(t *testing.T, frames []runtime.ProviderTunnelFrame) {
	t.Helper()
	if len(frames) == 0 {
		t.Fatal("no tunnel frames emitted")
	}
	terminals := 0
	for i, f := range frames {
		if f.Sequence != int64(i) {
			t.Errorf("frame %d: sequence %d, want %d (ordered, lossless)", i, f.Sequence, i)
		}
		switch f.Kind {
		case runtime.ProviderTunnelFrameKindEnd, runtime.ProviderTunnelFrameKindError:
			terminals++
			if i != len(frames)-1 {
				t.Errorf("terminal %s frame at index %d must be the last of %d frames", f.Kind, i, len(frames))
			}
		}
	}
	if terminals != 1 {
		t.Errorf("expected exactly one terminal END/ERROR frame, got %d", terminals)
	}
}
