package testutil

import (
	"context"
	"os"
	"runtime"
	"strings"
	"sync"
	"testing"
	"time"

	noderuntime "iop/apps/node/internal/runtime"
)

type FakeSink struct {
	mu     sync.Mutex
	events []noderuntime.RuntimeEvent
}

func (f *FakeSink) Emit(_ context.Context, e noderuntime.RuntimeEvent) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.events = append(f.events, e)
	return nil
}

func (f *FakeSink) Events() []noderuntime.RuntimeEvent {
	f.mu.Lock()
	defer f.mu.Unlock()
	cp := make([]noderuntime.RuntimeEvent, len(f.events))
	copy(cp, f.events)
	return cp
}

func RequireUnixShell(t *testing.T) {
	t.Helper()
	if runtime.GOOS == "windows" {
		t.Skip("Unix shell required")
	}
}

func RequirePTYSupport(t *testing.T) {
	t.Helper()
	if runtime.GOOS == "windows" {
		t.Skip("PTY not supported on Windows")
	}
}

func CollectDeltas(events []noderuntime.RuntimeEvent) string {
	deltas := make([]string, 0, len(events))
	for _, e := range events {
		if e.Type == noderuntime.EventTypeDelta {
			deltas = append(deltas, e.Delta)
		}
	}
	return strings.Join(deltas, "")
}

func RequireEventually(t *testing.T, timeout, interval time.Duration, fn func() bool) {
	t.Helper()

	timer := time.NewTimer(timeout)
	defer timer.Stop()
	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	for {
		if fn() {
			return
		}
		select {
		case <-timer.C:
			t.Fatal("condition not satisfied within timeout")
		case <-ticker.C:
		}
	}
}

func ReadMarker(path string) string {
	data, err := os.ReadFile(path)
	if err != nil {
		return ""
	}
	return strings.TrimSpace(string(data))
}
