package actor

import (
	"errors"
	"reflect"
	"slices"
	"testing"

	"git.tornberg.me/go-cart-actor/pkg/messages"
)

type cartState struct {
	calls     int
	lastAdded *messages.AddItem
}

func TestRegisteredMutationBasics(t *testing.T) {
	reg := NewMutationRegistry().(*ProtoMutationRegistry)

	addItemMutation := NewMutation(
		func(state *cartState, msg *messages.AddItem) error {
			state.calls++
			// copy to avoid external mutation side-effects (not strictly necessary for the test)
			cp := *msg
			state.lastAdded = &cp
			return nil
		},
		func() *messages.AddItem { return &messages.AddItem{} },
	)

	// Sanity check on mutation metadata
	if addItemMutation.Name() != "AddItem" {
		t.Fatalf("expected mutation Name() == AddItem, got %s", addItemMutation.Name())
	}
	if got, want := addItemMutation.Type(), reflect.TypeOf(messages.AddItem{}); got != want {
		t.Fatalf("expected Type() == %v, got %v", want, got)
	}

	reg.RegisterMutations(addItemMutation)

	// RegisteredMutations: membership (order not guaranteed)
	names := reg.RegisteredMutations()
	if !slices.Contains(names, "AddItem") {
		t.Fatalf("RegisteredMutations missing AddItem, got %v", names)
	}

	// RegisteredMutationTypes: membership (order not guaranteed)
	types := reg.RegisteredMutationTypes()
	if !slices.Contains(types, reflect.TypeOf(messages.AddItem{})) {
		t.Fatalf("RegisteredMutationTypes missing AddItem type, got %v", types)
	}

	// GetTypeName should resolve for a pointer instance
	name, ok := reg.GetTypeName(&messages.AddItem{})
	if !ok || name != "AddItem" {
		t.Fatalf("GetTypeName returned (%q,%v), expected (AddItem,true)", name, ok)
	}

	// GetTypeName should fail for unregistered type
	if name, ok := reg.GetTypeName(&messages.Noop{}); ok || name != "" {
		t.Fatalf("expected GetTypeName to fail for unregistered message, got (%q,%v)", name, ok)
	}

	// Create by name
	msg, ok := reg.Create("AddItem")
	if !ok {
		t.Fatalf("Create failed for registered mutation")
	}
	if _, isAddItem := msg.(*messages.AddItem); !isAddItem {
		t.Fatalf("Create returned wrong concrete type: %T", msg)
	}

	// Create unknown
	if m2, ok := reg.Create("Unknown"); ok || m2 != nil {
		t.Fatalf("Create should fail for unknown mutation, got (%T,%v)", m2, ok)
	}

	// Apply happy path
	state := &cartState{}
	add := &messages.AddItem{ItemId: 42, Quantity: 3, Sku: "ABC"}
	if _, err := reg.Apply(state, add); err != nil {
		t.Fatalf("Apply returned error: %v", err)
	}
	if state.calls != 1 {
		t.Fatalf("handler not invoked expected calls=1 got=%d", state.calls)
	}
	if state.lastAdded == nil || state.lastAdded.ItemId != 42 || state.lastAdded.Quantity != 3 {
		t.Fatalf("state not updated correctly: %+v", state.lastAdded)
	}

	// Apply nil grain
	if _, err := reg.Apply(nil, add); err == nil {
		t.Fatalf("expected error for nil grain")
	}

	// Apply nil message
	if _, err := reg.Apply(state, nil); err == nil {
		t.Fatalf("expected error for nil mutation message")
	}

	// Apply unregistered message
	if _, err := reg.Apply(state, &messages.Noop{}); !errors.Is(err, ErrMutationNotRegistered) {
		t.Fatalf("expected ErrMutationNotRegistered, got %v", err)
	}
}

// func TestConcurrentSafeRegistrationLookup(t *testing.T) {
// 	// This test is light-weight; it ensures locks don't deadlock under simple concurrent access.
// 	reg := NewMutationRegistry().(*ProtoMutationRegistry)
// 	mut := NewMutation[cartState, *messages.Noop](
// 		func(state *cartState, msg *messages.Noop) error { state.calls++; return nil },
// 		func() *messages.Noop { return &messages.Noop{} },
// 	)
// 	reg.RegisterMutations(mut)

// 	done := make(chan struct{})
// 	const workers = 25
// 	for i := 0; i < workers; i++ {
// 		go func() {
// 			for j := 0; j < 100; j++ {
// 				_, _ = reg.Create("Noop")
// 				_, _ = reg.GetTypeName(&messages.Noop{})
// 				_ = reg.Apply(&cartState{}, &messages.Noop{})
// 			}
// 			done <- struct{}{}
// 		}()
// 	}

// 	for i := 0; i < workers; i++ {
// 		<-done
// 	}
// }

// Helpers
