package agent

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

	"reasonix/internal/event"
	"reasonix/internal/provider"
	"reasonix/internal/tool"
)

// stubHooks blocks PreToolUse for named tools and records what it saw.
type stubHooks struct {
	blockPre      map[string]bool
	preSeen       []string
	postSeen      []string
	preCompactOut string   // returned from PreCompact (extra summary guidance)
	subagentSeen  []string // last-answer text passed to each SubagentStop
}

func (h *stubHooks) PreToolUse(_ context.Context, name string, _ json.RawMessage) (bool, string) {
	h.preSeen = append(h.preSeen, name)
	if h.blockPre[name] {
		return true, "blocked by test hook"
	}
	return false, ""
}

func (h *stubHooks) PostToolUse(_ context.Context, name string, _ json.RawMessage, _ string) {
	h.postSeen = append(h.postSeen, name)
}

func (h *stubHooks) SubagentStop(_ context.Context, last string) {
	h.subagentSeen = append(h.subagentSeen, last)
}
func (h *stubHooks) PreCompact(context.Context, string) string { return h.preCompactOut }

// TestSubagentStopFiresForForegroundTask checks SubagentStop fires (with the
// sub-agent's answer) when a foreground `task` call completes, but not for a
// backgrounded one (which only returns a "started" handle and stops later).
func TestSubagentStopFiresForForegroundTask(t *testing.T) {
	reg := tool.NewRegistry()
	reg.Add(okTool{name: "task"}) // stands in for the real task tool; returns "ok"
	h := &stubHooks{}
	a := New(nil, reg, NewSession(""), Options{Hooks: h}, event.Discard)

	a.executeBatch(context.Background(), []provider.ToolCall{{Name: "task", Arguments: `{"prompt":"x"}`}})
	if len(h.subagentSeen) != 1 || h.subagentSeen[0] != "ok" {
		t.Fatalf("foreground task should fire SubagentStop with the answer, saw %v", h.subagentSeen)
	}

	a.executeBatch(context.Background(), []provider.ToolCall{{Name: "task", Arguments: `{"run_in_background":true}`}})
	if len(h.subagentSeen) != 1 {
		t.Errorf("backgrounded task must not fire SubagentStop, saw %v", h.subagentSeen)
	}
}

// TestPreToolUseHookBlocks proves a gating PreToolUse hook refuses a tool call
// (returning a blocked result, never running the tool or its PostToolUse), while
// an unblocked call runs and fires PostToolUse.
func TestPreToolUseHookBlocks(t *testing.T) {
	reg := tool.NewRegistry()
	reg.Add(fakeTool{name: "bash", readOnly: false})
	reg.Add(fakeTool{name: "read_file", readOnly: true})

	h := &stubHooks{blockPre: map[string]bool{"bash": true}}
	a := New(nil, reg, NewSession(""), Options{Hooks: h}, event.Discard)

	blocked := a.executeOne(context.Background(), provider.ToolCall{Name: "bash", Arguments: `{"command":"x"}`})
	if !blocked.blocked || !strings.HasPrefix(blocked.output, "blocked:") {
		t.Errorf("PreToolUse block should yield a blocked result, got %+v", blocked)
	}
	if !strings.Contains(blocked.output, "blocked by test hook") {
		t.Errorf("block reason should be surfaced to the model, got %q", blocked.output)
	}

	ok := a.executeOne(context.Background(), provider.ToolCall{Name: "read_file", Arguments: `{"path":"/a"}`})
	if ok.blocked || !strings.Contains(ok.output, "done") {
		t.Errorf("unblocked call should run, got %+v", ok)
	}

	if got := strings.Join(h.preSeen, ","); got != "bash,read_file" {
		t.Errorf("PreToolUse should fire for both calls, saw %q", got)
	}
	// PostToolUse fires only for the call that actually ran.
	if got := strings.Join(h.postSeen, ","); got != "read_file" {
		t.Errorf("PostToolUse should fire only for the run tool, saw %q", got)
	}
}
