package sandbox

import (
	"context"
	"encoding/json"
	"strings"
	"sync"
	"testing"

	"go.starlark.net/starlark"

	"armature/events"
)

func TestRealtimePublish_HappyPath(t *testing.T) {
	bus := events.NewBus()

	var mu sync.Mutex
	var received events.Event

	bus.Subscribe("realtime.*", func(e events.Event) {
		mu.Lock()
		received = e
		mu.Unlock()
	})

	mod := BuildRealtimeModule(context.Background(), bus, "test-pkg")
	sb := New(DefaultConfig())

	_, err := sb.Exec(context.Background(), "test.star", `
result = realtime.publish("room:123", "chat.message", {"text": "hello", "from": "alice"})
`, map[string]starlark.Value{"realtime": mod})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	mu.Lock()
	defer mu.Unlock()

	if received.Label != "realtime.chat.message" {
		t.Errorf("expected label 'realtime.chat.message', got %q", received.Label)
	}
	if received.Room != "room:123" {
		t.Errorf("expected room 'room:123', got %q", received.Room)
	}

	var payload map[string]any
	if err := json.Unmarshal(received.Payload, &payload); err != nil {
		t.Fatalf("payload unmarshal error: %v", err)
	}
	if payload["text"] != "hello" {
		t.Errorf("expected text 'hello', got %v", payload["text"])
	}
	if payload["from"] != "alice" {
		t.Errorf("expected from 'alice', got %v", payload["from"])
	}
	if payload["_pkg"] != "test-pkg" {
		t.Errorf("expected _pkg 'test-pkg', got %v", payload["_pkg"])
	}
}

func TestRealtimePublish_EmptyData(t *testing.T) {
	bus := events.NewBus()
	var received events.Event
	bus.Subscribe("realtime.*", func(e events.Event) { received = e })

	mod := BuildRealtimeModule(context.Background(), bus, "my-pkg")
	sb := New(DefaultConfig())

	_, err := sb.Exec(context.Background(), "test.star", `
realtime.publish("ch:1", "heartbeat")
`, map[string]starlark.Value{"realtime": mod})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	var payload map[string]any
	json.Unmarshal(received.Payload, &payload)
	if payload["_pkg"] != "my-pkg" {
		t.Errorf("expected _pkg 'my-pkg', got %v", payload["_pkg"])
	}
	// Should only have _pkg
	if len(payload) != 1 {
		t.Errorf("expected 1 key (_pkg only), got %d", len(payload))
	}
}

func TestRealtimePublish_MissingArgs(t *testing.T) {
	bus := events.NewBus()
	mod := BuildRealtimeModule(context.Background(), bus, "pkg")
	sb := New(DefaultConfig())

	// Missing event
	_, err := sb.Exec(context.Background(), "test.star", `
realtime.publish("ch:1")
`, map[string]starlark.Value{"realtime": mod})
	if err == nil {
		t.Fatal("expected error for missing event arg")
	}
}

func TestRealtimePublish_ReservedPrefix(t *testing.T) {
	bus := events.NewBus()
	mod := BuildRealtimeModule(context.Background(), bus, "pkg")
	sb := New(DefaultConfig())

	_, err := sb.Exec(context.Background(), "test.star", `
realtime.publish("ch:1", "system.notify", {})
`, map[string]starlark.Value{"realtime": mod})
	if err == nil {
		t.Fatal("expected error for reserved prefix")
	}
	if !strings.Contains(err.Error(), "reserved prefix") {
		t.Errorf("error should mention reserved prefix, got: %v", err)
	}
}

func TestRealtimePublish_PayloadTooLarge(t *testing.T) {
	bus := events.NewBus()
	mod := BuildRealtimeModule(context.Background(), bus, "pkg")
	sb := New(DefaultConfig())

	// Build a large string via repeated concatenation in Starlark
	_, err := sb.Exec(context.Background(), "test.star", `
big = "x" * 8000
realtime.publish("ch:1", "big.event", {"data": big})
`, map[string]starlark.Value{"realtime": mod})
	if err == nil {
		t.Fatal("expected error for payload too large")
	}
	if !strings.Contains(err.Error(), "too large") {
		t.Errorf("error should mention 'too large', got: %v", err)
	}
}

func TestStarlarkToGoVal(t *testing.T) {
	tests := []struct {
		name string
		val  starlark.Value
		want any
	}{
		{"none", starlark.None, nil},
		{"true", starlark.True, true},
		{"false", starlark.False, false},
		{"int", starlark.MakeInt(42), int64(42)},
		{"float", starlark.Float(3.14), float64(3.14)},
		{"string", starlark.String("hello"), "hello"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := starlarkToGoVal(tt.val)
			if got != tt.want {
				t.Errorf("starlarkToGoVal(%s) = %v (%T), want %v (%T)", tt.val, got, got, tt.want, tt.want)
			}
		})
	}
}
