package main

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

	"github.com/kami/maven/internal/ipc"
	"github.com/kami/maven/internal/router"
)

// These tests exercise the fake ecosystem harness (fakeecosystem_test.go)
// directly, covering paths the ad-hoc httptest servers in ecosystem_test.go
// don't: Praxis attention (happy + degraded) and fault injection against a
// reusable fake rather than a one-off inline handler.

func praxisActDec(fn string) router.Decision {
	return router.Decision{Intent: router.IntentAct, Slots: router.Slots{Fn: fn, HasFn: true}}
}

// praxisItemDec is praxisActDec for the lifecycle verbs, which need an item id
// in the value slot. Without one they answer "which item?" and never reach
// Praxis at all, which makes them useless for testing a Praxis outage.
func praxisItemDec(fn, itemID string) router.Decision {
	return router.Decision{Intent: router.IntentAct, Slots: router.Slots{Fn: fn, HasFn: true, Value: itemID}}
}

func newPraxisTestHandler(t *testing.T, praxis *fakeServer) *reactiveHandler {
	t.Helper()
	st := newTestStore(t)
	clock := newFakeClock(time.Now())
	return &reactiveHandler{
		api:       ipc.NewStoreAPI(st),
		dataStore: st,
		now:       clock.Now,
		ecosystem: &ecosystemWiring{praxis: newPraxisClient(praxis.URL)},
	}
}

func TestPraxisAttention_HappyPathSurfacesItems(t *testing.T) {
	ctx := context.Background()
	items := fixturePraxisAttentionItems(map[string]any{
		"id": "item_1", "title": "disk almost full", "importance": 3.0, "rule": "low_disk",
	})
	praxis := newFakePraxis(t, items)
	h := newPraxisTestHandler(t, praxis)

	reply := h.handlePraxisAct(ctx, praxisActDec("list_attention"))
	if !strings.Contains(reply, "disk almost full") {
		t.Fatalf("expected attention digest to mention the item, got %q", reply)
	}

	var sawAttention, sawSurface bool
	for _, r := range praxis.Requests() {
		if r.Method == "GET" && strings.HasPrefix(r.Path, "/api/v1/tools/attention") {
			sawAttention = true
		}
		if r.Method == "POST" && r.Path == "/api/v1/tools/surface" {
			sawSurface = true
		}
	}
	if !sawAttention {
		t.Error("expected a GET to /api/v1/tools/attention")
	}
	if !sawSurface {
		t.Error("expected surfaced item to POST /api/v1/tools/surface (surfaced != acknowledged)")
	}
}

// TestPraxisAttention_DegradedFailsClosedNotEmpty covers the degraded-mode
// contract: when Praxis is down, Maven must say so rather than silently
// returning nothing or panicking.
func TestPraxisAttention_DegradedFailsClosedNotEmpty(t *testing.T) {
	ctx := context.Background()
	praxis := newFakePraxis(t, fixturePraxisAttentionItems())
	praxis.SetFault(500)
	h := newPraxisTestHandler(t, praxis)

	reply := h.handlePraxisAct(ctx, praxisActDec("list_attention"))
	if reply == "" {
		t.Fatal("praxis outage must not produce an empty reply")
	}
	if strings.Contains(reply, "disk almost full") {
		t.Fatal("degraded reply must not fabricate item content")
	}
}

// TestFakeNexus_FaultInjectionThenRecovery demonstrates the shared harness's
// fault toggle affecting the same running server, matching the shape of a
// real dependency flapping and recovering mid-session.
func TestFakeNexus_FaultInjectionThenRecovery(t *testing.T) {
	ctx := context.Background()
	nexus := newFakeNexus(t, fixtureNexusResolved("ent_muzick", "Muzick indexer", "service"))
	caps := fixtureHexisCapabilities(map[string]any{"id": "cap_status", "name": "restart", "read_only": true})
	hexis := newFakeHexis(t, caps, fixtureHexisExecuted("exec_1", "succeeded"))

	st := newTestStore(t)
	h := &reactiveHandler{
		api:       ipc.NewStoreAPI(st),
		dataStore: st,
		now:       time.Now,
		ecosystem: stubEcosystem(nexus.URL, hexis.URL),
	}

	nexus.SetFault(503)
	reply := h.handleHexisAct(ctx, actDec("muzick indexer"))
	if actRan(reply) {
		t.Fatalf("nexus outage must not report success, got %q", reply)
	}

	nexus.SetFault(0)
	reply = h.handleHexisAct(ctx, actDec("muzick indexer"))
	if !actRan(reply) {
		t.Fatalf("expected success once nexus recovers, got %q", reply)
	}
}
