package exec

import (
	"encoding/json"
	"os/exec"
	"strings"
	"testing"
)

func TestRenderRootHelp(t *testing.T) {
	r, err := DefaultCatalog()
	if err != nil {
		t.Fatal(err)
	}
	out := r.RenderRootHelp()
	if !strings.Contains(out, "workspace") || !strings.Contains(out, "profile") ||
		!strings.Contains(out, "role") {
		t.Fatalf("root help missing domains:\n%s", out)
	}
	if !strings.Contains(out, "agentteam exec <domain> <action> [<json|->]") {
		t.Fatalf("root help missing usage:\n%s", out)
	}
	for _, want := range []string{"task create", "workflow run", "role list", "role apply", "--error-schema", "validation=64", "internal=70"} {
		if !strings.Contains(out, want) {
			t.Fatalf("root help missing %q:\n%s", want, out)
		}
	}
}

func TestRenderRoleHelpExposesExactSevenActions(t *testing.T) {
	r, err := DefaultCatalog()
	if err != nil {
		t.Fatal(err)
	}
	out, ok := r.RenderDomainHelp("role")
	if !ok {
		t.Fatal("expected role domain")
	}
	for _, action := range []string{"list", "get", "request-get", "create", "update", "delete", "apply"} {
		if !strings.Contains(out, "  "+action+" —") {
			t.Errorf("role domain help missing %q:\n%s", action, out)
		}
		actionHelp, found := r.RenderActionHelp("role", action)
		if !found || !strings.Contains(actionHelp, "Usage: agentteam exec role "+action+" <json|->") {
			t.Errorf("role %s action help missing or invalid:\n%s", action, actionHelp)
		}
	}
}

func TestRenderWorkflowActionHelpListsOperations(t *testing.T) {
	r, err := DefaultCatalog()
	if err != nil {
		t.Fatal(err)
	}
	out, ok := r.RenderActionHelp("workflow", "plan")
	if !ok || !strings.Contains(out, "Operations: list, show, validate, create, update, publish, archive") {
		t.Fatalf("workflow help missing operations:\n%s", out)
	}
}

func TestWorkflowCatalogExposesOnlyBusinessAgentAuthorization(t *testing.T) {
	r, err := DefaultCatalog()
	if err != nil {
		t.Fatal(err)
	}
	for object, operations := range map[string]string{
		"trigger": "Operations: list, show, validate, create, update, enable, disable, archive",
		"run":     "Operations: list, start, show, logs, cancel",
		"step":    "Operations: list, get, complete, fail, pause, park",
	} {
		out, ok := r.RenderActionHelp("workflow", object)
		if !ok || !strings.Contains(out, operations) {
			t.Fatalf("workflow %s help does not expose only %q:\n%s", object, operations, out)
		}
	}
}

func TestRenderDomainHelp(t *testing.T) {
	r, err := DefaultCatalog()
	if err != nil {
		t.Fatal(err)
	}
	out, ok := r.RenderDomainHelp("profile")
	if !ok {
		t.Fatal("expected profile domain")
	}
	if !strings.Contains(out, "set-avatar") {
		t.Fatalf("domain help missing actions:\n%s", out)
	}
	if _, ok := r.RenderDomainHelp("nope"); ok {
		t.Fatal("expected unknown domain to miss")
	}
}

func TestRenderActionHelp(t *testing.T) {
	r, err := DefaultCatalog()
	if err != nil {
		t.Fatal(err)
	}
	out, ok := r.RenderActionHelp("profile", "set-avatar")
	if !ok {
		t.Fatal("expected action")
	}
	for _, want := range []string{
		"profile set-avatar",
		"Usage: agentteam exec profile set-avatar <json|->",
		"Example:",
		"--input-schema",
		"--output-schema",
	} {
		if !strings.Contains(out, want) {
			t.Errorf("action help missing %q:\n%s", want, out)
		}
	}
	for _, unwanted := range []string{"Permission:", "Inputs:", "Required:", "Errors:", "Result:"} {
		if strings.Contains(out, unwanted) {
			t.Errorf("action help unexpectedly contains %q:\n%s", unwanted, out)
		}
	}
	if _, ok := r.RenderActionHelp("profile", "nope"); ok {
		t.Fatal("expected unknown action to miss")
	}
}

func TestWorkflowPlanHelpDiscoversTrustedThreadHandoff(t *testing.T) {
	r, err := DefaultCatalog()
	if err != nil {
		t.Fatal(err)
	}
	out, ok := r.RenderActionHelp("workflow", "plan")
	if !ok {
		t.Fatal("expected workflow plan action")
	}
	for _, want := range []string{
		`"action":"create"`,
		`startThread: true`,
		"output.threadScope",
		"completion: callback",
	} {
		if !strings.Contains(out, want) {
			t.Errorf("workflow plan help missing %q:\n%s", want, out)
		}
	}
}

func TestRenderActionInputAndOutputSchemaJSONOnly(t *testing.T) {
	r, err := DefaultCatalog()
	if err != nil {
		t.Fatal(err)
	}
	out, ok := r.RenderActionInputSchemaJSON("profile", "set-avatar")
	if !ok {
		t.Fatal("expected action input schema")
	}
	var schema map[string]any
	if err := json.Unmarshal([]byte(out), &schema); err != nil {
		t.Fatalf("schema JSON must parse: %v\n%s", err, out)
	}
	if schema["type"] != "object" {
		t.Fatalf("input schema type = %v, want object", schema["type"])
	}
	if strings.Contains(out, `"output"`) || strings.Contains(out, `"error"`) || strings.Contains(out, `"examples"`) {
		t.Fatalf("input schema must not include envelope fields:\n%s", out)
	}
	out, ok = r.RenderActionOutputSchemaJSON("profile", "set-avatar")
	if !ok || !strings.Contains(out, `"avatarUrl"`) || strings.Contains(out, `"file_path"`) {
		t.Fatalf("unexpected output schema:\n%s", out)
	}
	if _, ok := r.RenderActionInputSchemaJSON("profile", "missing"); ok {
		t.Fatal("expected unknown action to miss")
	}
}

// TestHeredocShellRoundTrip proves quoted, collision-free delimiters preserve
// arbitrary JSON example content through a real shell.
func TestHeredocShellRoundTrip(t *testing.T) {
	for _, raw := range []string{
		`{"msg":"it's time"}`,
		`{"msg":"line1\nline2","u":"日本語"}`,
		`{"a":"EOF"}`,
		`{"a":"AGENTTEAMEXEC"}`,
	} {
		content := compactJSON([]byte(raw))
		d := heredocDelimFor(content)
		script := "cat <<'" + d + "'\n" + content + "\n" + d
		out, err := exec.Command("sh", "-c", script).Output()
		if err != nil {
			t.Fatalf("sh round-trip failed for %s: %v", raw, err)
		}
		if got := strings.TrimSpace(string(out)); got != content {
			t.Errorf("round-trip mismatch for %s:\nwant %q\ngot  %q", raw, content, got)
		}
	}
}

func TestRenderActionHelpInlineExample(t *testing.T) {
	descriptor, err := describe(actionSpec[contractTestInput, contractTestOutput]{
		Meta:     actionMeta{Domain: "x", Name: "send", Summary: "s", Permission: permissionAgentScopedMutation},
		Examples: []actionExample[contractTestInput]{{Title: "apostrophe", Input: contractTestInput{Channel: "it's time"}}},
	})
	if err != nil {
		t.Fatal(err)
	}
	catalog, err := newDescriptorCatalog(descriptor)
	if err != nil {
		t.Fatal(err)
	}
	out, ok := catalog.RenderActionHelp("x", "send")
	if !ok || !strings.Contains(out, `Example: {"channel":"it's time"}`) || strings.Contains(out, "AGENTTEAMEXEC") {
		t.Fatalf("unexpected inline example rendering:\n%s", out)
	}
}

func TestRenderActionHelpOneOfRequired(t *testing.T) {
	lines := requiredLines(json.RawMessage(`{"type":"object","oneOf":[{"required":["type","target","requestId","proposal"]}]}`))
	if len(lines) != 1 || !strings.Contains(lines[0], "type, target, requestId, proposal") {
		t.Fatalf("oneOf branch required not rendered: %#v", lines)
	}
}
