package plugins

import (
	"context"
	"strings"
	"testing"
)

func TestBuiltinMemory_Metadata(t *testing.T) {
	p := NewBuiltinMemoryPlugin()
	if p.Name() != "@memory" {
		t.Errorf("name = %q", p.Name())
	}
	if p.Description() == "" || p.Usage() == "" || p.Version() == "" {
		t.Error("metadata strings must be non-empty")
	}
	if p.Path() != "" {
		t.Errorf("builtin path should be empty, got %q", p.Path())
	}
	schema := p.Schema()
	for _, want := range []string{"remember", "profile", "forget", "recall", "neighbors", "map", "subcommands"} {
		if !strings.Contains(schema, want) {
			t.Errorf("schema missing %q", want)
		}
	}
}

func TestBuiltinMemory_MoreDispatch(t *testing.T) {
	fake := &fakeMemAdapter{}
	SetMemoryAdapter(fake)
	t.Cleanup(func() { SetMemoryAdapter(nil) })

	p := NewBuiltinMemoryPlugin()
	ctx := context.Background()

	// recall
	if _, err := p.Execute(ctx, []string{`{"cmd":"recall","args":{"query":"certs"}}`}); err != nil {
		t.Fatalf("recall: %v", err)
	}
	if fake.lastRecall != "certs" {
		t.Errorf("recall query not propagated: %q", fake.lastRecall)
	}

	// profile with the bare (no "fields" wrapper) form
	if _, err := p.Execute(ctx, []string{`{"cmd":"profile","args":{"role":"SRE"}}`}); err != nil {
		t.Fatalf("profile bare: %v", err)
	}
	if fake.lastProfile["role"] != "SRE" {
		t.Errorf("bare profile form not parsed: %v", fake.lastProfile)
	}

	// argv (flag) form instead of JSON envelope
	if _, err := p.Execute(ctx, []string{"remember", "--content", "argv fact"}); err != nil {
		t.Fatalf("argv form: %v", err)
	}
	if fake.lastRemember[0] != "argv fact" {
		t.Errorf("argv form not parsed: %v", fake.lastRemember)
	}

	// graph folded into @memory: neighbors routes the query through to the graph
	if _, err := p.Execute(ctx, []string{`{"cmd":"neighbors","args":{"query":"auth"}}`}); err != nil {
		t.Fatalf("neighbors: %v", err)
	}
	if fake.lastNeighbors != "auth" {
		t.Errorf("neighbors query not propagated: %q", fake.lastNeighbors)
	}
	// map needs no args
	if out, err := p.Execute(ctx, []string{`{"cmd":"map"}`}); err != nil || out != "map" {
		t.Fatalf("map: out=%q err=%v", out, err)
	}
	// neighbors without query -> error
	if _, err := p.Execute(ctx, []string{`{"cmd":"neighbors","args":{}}`}); err == nil {
		t.Error("expected error for neighbors without query")
	}

	// unknown cmd -> error
	if _, err := p.Execute(ctx, []string{`{"cmd":"bogus","args":{}}`}); err == nil {
		t.Error("expected error for unknown cmd")
	}
	// forget without match -> error
	if _, err := p.Execute(ctx, []string{`{"cmd":"forget","args":{}}`}); err == nil {
		t.Error("expected error for forget without match")
	}
}

func TestCanonicalMemoryCmdAliases(t *testing.T) {
	cases := map[string]string{
		"add": "remember", "store": "remember", "note": "remember",
		"user": "profile", "remove": "forget", "delete": "forget",
		"read": "recall", "get": "recall", "bogus": "",
	}
	for in, want := range cases {
		if got := canonicalMemoryCmd(in); got != want {
			t.Errorf("canonicalMemoryCmd(%q) = %q, want %q", in, got, want)
		}
	}
}

func TestStringifyMap(t *testing.T) {
	out := stringifyMap(map[string]interface{}{
		"a": "x", "n": float64(3), "b": true, "list": []interface{}{"p", "q"}, "skip": nil,
	})
	if out["a"] != "x" || out["n"] != "3" || out["b"] != "true" || out["list"] != "p, q" {
		t.Errorf("stringifyMap wrong: %v", out)
	}
	if _, ok := out["skip"]; ok {
		t.Error("nil values should be skipped")
	}
}

func TestParseMemoryInvocation_Envelope(t *testing.T) {
	cmd, inner, err := parseMemoryInvocation([]string{`{"cmd":"remember","args":{"content":"x","category":"personal"}}`})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if cmd != "remember" {
		t.Errorf("cmd = %q, want remember", cmd)
	}
	if !strings.Contains(inner, `"content":"x"`) {
		t.Errorf("inner missing content: %s", inner)
	}
}

func TestParseMemoryInvocation_Aliases(t *testing.T) {
	for _, alias := range []string{"add", "store", "note"} {
		cmd, _, err := parseMemoryInvocation([]string{`{"cmd":"` + alias + `","args":{}}`})
		if err != nil || cmd != "remember" {
			t.Errorf("alias %q -> (%q,%v), want remember", alias, cmd, err)
		}
	}
}

func TestParseProfileFields(t *testing.T) {
	// Wrapped form.
	f, err := parseProfileFields(`{"fields":{"role":"SRE","certifications":["AWS","CKA"]}}`)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if f["role"] != "SRE" {
		t.Errorf("role = %q", f["role"])
	}
	if f["certifications"] != "AWS, CKA" {
		t.Errorf("array should join with comma, got %q", f["certifications"])
	}

	// Bare form (no wrapper).
	f2, err := parseProfileFields(`{"company":"Acme"}`)
	if err != nil || f2["company"] != "Acme" {
		t.Errorf("bare form failed: %v / %v", f2, err)
	}
}

// fakeMemAdapter records calls for dispatch testing.
type fakeMemAdapter struct {
	lastRemember  [2]string
	lastProfile   map[string]string
	lastForget    string
	lastRecall    string
	lastNeighbors string
}

func (f *fakeMemAdapter) Remember(content, category string) (string, error) {
	f.lastRemember = [2]string{content, category}
	return "ok", nil
}
func (f *fakeMemAdapter) UpdateProfile(u map[string]string) (string, error) {
	f.lastProfile = u
	return "ok", nil
}
func (f *fakeMemAdapter) Forget(m string) (string, error) { f.lastForget = m; return "ok", nil }
func (f *fakeMemAdapter) Recall(q string) (string, error) { f.lastRecall = q; return "ok", nil }
func (f *fakeMemAdapter) GraphMap() (string, error)       { return "map", nil }
func (f *fakeMemAdapter) GraphNeighbors(q string) (string, error) {
	f.lastNeighbors = q
	return "neighbors", nil
}

func TestBuiltinMemory_Dispatch(t *testing.T) {
	fake := &fakeMemAdapter{}
	SetMemoryAdapter(fake)
	t.Cleanup(func() { SetMemoryAdapter(nil) })

	p := NewBuiltinMemoryPlugin()
	ctx := context.Background()

	if _, err := p.Execute(ctx, []string{`{"cmd":"remember","args":{"content":"User got AWS cert","category":"personal"}}`}); err != nil {
		t.Fatalf("remember: %v", err)
	}
	if fake.lastRemember[0] != "User got AWS cert" || fake.lastRemember[1] != "personal" {
		t.Errorf("remember dispatch wrong: %v", fake.lastRemember)
	}

	if _, err := p.Execute(ctx, []string{`{"cmd":"profile","args":{"fields":{"company":"Acme"}}}`}); err != nil {
		t.Fatalf("profile: %v", err)
	}
	if fake.lastProfile["company"] != "Acme" {
		t.Errorf("profile dispatch wrong: %v", fake.lastProfile)
	}

	if _, err := p.Execute(ctx, []string{`{"cmd":"forget","args":{"match":"tabs"}}`}); err != nil {
		t.Fatalf("forget: %v", err)
	}
	if fake.lastForget != "tabs" {
		t.Errorf("forget dispatch wrong: %q", fake.lastForget)
	}

	// Missing required field is a clean error, not a panic.
	if _, err := p.Execute(ctx, []string{`{"cmd":"remember","args":{}}`}); err == nil {
		t.Error("expected error for remember without content")
	}
}

func TestBuiltinMemory_NoAdapter(t *testing.T) {
	SetMemoryAdapter(nil)
	p := NewBuiltinMemoryPlugin()
	if _, err := p.Execute(context.Background(), []string{`{"cmd":"recall","args":{}}`}); err == nil {
		t.Error("expected error when no adapter is wired")
	}
}
