package main

import (
	"reflect"
	"testing"

	"github.com/kami/maven/internal/decision"
	"github.com/kami/maven/internal/event"
	"github.com/kami/maven/internal/ipc"
	"github.com/kami/maven/internal/store"
	"github.com/kami/maven/internal/voice"
)

// fullDeps — a deployment with every optional piece present. Nothing here is
// run: newDaemonAPI takes method values and backgroundWorkers is pure, so
// zero-value wirings are enough to say what WOULD be started.
func fullDeps() bootDeps {
	h := &reactiveHandler{
		ecosystem: &ecosystemWiring{nexus: &nexusClient{}},
		decisions: decision.NewRing(),
	}
	return bootDeps{
		coreFor:    func() ipc.CoreAPI { return ipc.UnimplementedCoreAPI{} },
		tl:         &tickLoop{},
		evBus:      event.NewBus(4),
		st:         &store.Store{},
		factWorker: &factEnrichmentWorker{},
		evalWorker: &memoryEvalWorker{},
		feedWkr:    &feedWorker{},
		crawlWkr:   &crawlWorker{},
		voiceW: &voiceWiring{
			server:  &voice.Server{},
			handler: h,
			mcp:     &mcpWiring{},
			home:    &homeWiring{},
		},
	}
}

// The unlock path used to build its own daemonAPI literal and leave nexus and
// getMCPServers nil (V-639). Both paths call newDaemonAPI now, so the drift
// that can still happen is a field added to the struct and not to the
// constructor. This catches that one, by name.
func TestNewDaemonAPISetsEveryField(t *testing.T) {
	prev := allowSeedOnStart
	allowSeedOnStart = true
	defer func() { allowSeedOnStart = prev }()

	api := newDaemonAPI(fullDeps())
	v := reflect.ValueOf(*api)
	for i := range v.NumField() {
		if v.Field(i).IsZero() {
			t.Errorf("newDaemonAPI left %s unset — a fully wired deployment must fill every field", v.Type().Field(i).Name)
		}
	}
}

// The handler is wired with the bare store adapter and cannot serve the day
// plan until upgradeAPI hands it the real one. The unlocked path did that and
// the unlock path did it too; keep it a property of the constructor.
func TestNewDaemonAPIUpgradesTheHandler(t *testing.T) {
	d := fullDeps()
	api := newDaemonAPI(d)
	if d.voiceW.handler.api != ipc.CoreAPI(api) {
		t.Fatal("newDaemonAPI did not hand the handler the API it built")
	}
}

// Every worker the daemon runs goes through startBackground, so shutdown can
// wait for it. The unlock path used to start seven of these as bare
// `go func()` under a shadowed WaitGroup.
func TestBackgroundWorkersFullSet(t *testing.T) {
	want := []string{"voice", "tick", "fact-enrichment", "memory-eval", "feed", "crawl", "mcp", "home"}
	var got []string
	for _, w := range backgroundWorkers(fullDeps()) {
		got = append(got, w.name)
	}
	if !reflect.DeepEqual(got, want) {
		t.Errorf("workers = %v, want %v", got, want)
	}
}

// A default box configures none of the optional blocks. Two workers always run
// and the rest stay dark, rather than a nil run being scheduled.
func TestBackgroundWorkersFloor(t *testing.T) {
	d := fullDeps()
	d.evalWorker, d.feedWkr, d.crawlWkr, d.voiceW = nil, nil, nil, nil
	want := []string{"tick", "fact-enrichment"}
	var got []string
	for _, w := range backgroundWorkers(d) {
		got = append(got, w.name)
	}
	if !reflect.DeepEqual(got, want) {
		t.Errorf("workers = %v, want %v", got, want)
	}
}
