package node

import (
	"bytes"
	"context"
	"strings"
	"testing"
	"time"

	"google.golang.org/protobuf/proto"

	"iop/apps/node/internal/runtime"
	iop "iop/proto/gen/iop"
)

// mockSender is a minimal mock for protoSender.
type mockSender struct {
	sent *iop.RunEvent
}

func (m *mockSender) Send(msg proto.Message) error {
	if re, ok := msg.(*iop.RunEvent); ok {
		m.sent = re
	}
	return nil
}

func TestSessionSinkEmitIncludesNodeID(t *testing.T) {
	ms := &mockSender{}
	sink := &sessionSink{
		sess:       ms,
		nodeID:     "test-node-123",
		sessionID:  "session-abc",
		background: true,
	}

	event := runtime.RuntimeEvent{
		RunID:     "run-xyz",
		Type:      runtime.EventTypeStart,
		Timestamp: time.Now(),
	}

	err := sink.Emit(context.Background(), event)
	if err != nil {
		t.Fatalf("Emit failed: %v", err)
	}

	if ms.sent == nil {
		t.Fatal("no event sent")
	}

	if ms.sent.NodeId != "test-node-123" {
		t.Errorf("expected NodeId %q, got %q", "test-node-123", ms.sent.NodeId)
	}
	if ms.sent.SessionId != "session-abc" {
		t.Errorf("expected SessionId %q, got %q", "session-abc", ms.sent.SessionId)
	}
	if !ms.sent.Background {
		t.Error("expected Background true")
	}
}

func TestSessionSinkPrintEventStreamsDeltaImmediately(t *testing.T) {
	ms := &mockSender{}
	var out bytes.Buffer
	sink := &sessionSink{
		sess:      ms,
		out:       &out,
		nodeID:    "test-node-123",
		sessionID: "session-abc",
	}

	start := runtime.RuntimeEvent{
		RunID:     "run-xyz",
		Type:      runtime.EventTypeStart,
		Timestamp: time.Now(),
	}
	delta1 := runtime.RuntimeEvent{
		RunID:     "run-xyz",
		Type:      runtime.EventTypeDelta,
		Delta:     "Hello",
		Timestamp: time.Now(),
	}
	delta2 := runtime.RuntimeEvent{
		RunID:     "run-xyz",
		Type:      runtime.EventTypeDelta,
		Delta:     " world",
		Timestamp: time.Now(),
	}
	complete := runtime.RuntimeEvent{
		RunID:     "run-xyz",
		Type:      runtime.EventTypeComplete,
		Message:   "done",
		Timestamp: time.Now(),
	}

	if err := sink.Emit(context.Background(), start); err != nil {
		t.Fatalf("Emit start failed: %v", err)
	}
	if err := sink.Emit(context.Background(), delta1); err != nil {
		t.Fatalf("Emit delta1 failed: %v", err)
	}
	got := out.String()
	if !strings.Contains(got, "[node-message] Hello") {
		t.Fatalf("expected first delta to be printed immediately, got %q", got)
	}

	if err := sink.Emit(context.Background(), delta2); err != nil {
		t.Fatalf("Emit delta2 failed: %v", err)
	}
	got = out.String()
	if !strings.Contains(got, "[node-message] Hello world") {
		t.Fatalf("expected second delta to append to stream, got %q", got)
	}

	if err := sink.Emit(context.Background(), complete); err != nil {
		t.Fatalf("Emit complete failed: %v", err)
	}
	got = out.String()
	if !strings.Contains(got, "[node-event] complete run_id=run-xyz detail=\"done\"") {
		t.Fatalf("expected complete event to be printed, got %q", got)
	}
	if strings.Count(got, "[node-message]") != 1 {
		t.Fatalf("expected node-message prefix once, got %q", got)
	}
}
