package signedjobs

import (
	"context"
	"encoding/json"
	"testing"
)

type fakeDecommissioner struct {
	got string
	err error
}

func (f *fakeDecommissioner) SetDecommissioned(durableID string) error {
	f.got = durableID
	return f.err
}

func TestDecommissionExecutor_HappyPath(t *testing.T) {
	fd := &fakeDecommissioner{}
	ex := NewDecommissionExecutor(fd, nil)
	err := ex.Execute(context.Background(), opDecommission, json.RawMessage(`{"durable_id":"uuid:abc-123"}`))
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if fd.got != "uuid:abc-123" {
		t.Errorf("SetDecommissioned got %q, want uuid:abc-123", fd.got)
	}
}

func TestDecommissionExecutor_NotOurOp(t *testing.T) {
	fd := &fakeDecommissioner{}
	ex := NewDecommissionExecutor(fd, nil)
	if err := ex.Execute(context.Background(), "storage_wipe", json.RawMessage(`{}`)); err != ErrNoExecutor {
		t.Errorf("want ErrNoExecutor for a foreign op, got %v", err)
	}
	if fd.got != "" {
		t.Errorf("SetDecommissioned must not be called for a foreign op (got %q)", fd.got)
	}
}

func TestDecommissionExecutor_RefusesUnbound(t *testing.T) {
	fd := &fakeDecommissioner{}
	ex := NewDecommissionExecutor(fd, nil)
	if err := ex.Execute(context.Background(), opDecommission, json.RawMessage(`{"durable_id":""}`)); err == nil {
		t.Error("want error for an empty durable_id (unbound decommission)")
	}
	if fd.got != "" {
		t.Errorf("SetDecommissioned must not be called for an unbound op (got %q)", fd.got)
	}
}

func TestDecommissionExecutor_NoIntentStore(t *testing.T) {
	ex := NewDecommissionExecutor(nil, nil)
	if err := ex.Execute(context.Background(), opDecommission, json.RawMessage(`{"durable_id":"uuid:x"}`)); err == nil {
		t.Error("want a hard error (not silent success) when no intent store is wired")
	}
}

// chainProbe records whether it was reached and what it returns.
type chainProbe struct {
	owns   string
	called bool
	ret    error
}

func (c *chainProbe) Execute(_ context.Context, op string, _ json.RawMessage) error {
	c.called = true
	if op == c.owns {
		return c.ret
	}
	return ErrNoExecutor
}

func TestExecutorChain_DispatchesToOwner(t *testing.T) {
	a := &chainProbe{owns: "storage_wipe"}
	b := &chainProbe{owns: "decommission"}
	chain := ExecutorChain{a, b}

	if err := chain.Execute(context.Background(), "decommission", nil); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !b.called {
		t.Error("owner (decommission) executor was not reached")
	}

	// An op no sub-executor owns → ErrNoExecutor (left queued).
	if err := chain.Execute(context.Background(), "guest_destroy", nil); err != ErrNoExecutor {
		t.Errorf("want ErrNoExecutor for an unowned op, got %v", err)
	}
}
