package tools

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

	"chat-switchboard/store"
)

// TestMain registers late-registered tools (notes) with nil deps so
// that Definition()-level tests work. Execute() tests need real deps.
func TestMain(m *testing.M) {
	RegisterNoteTools(store.Stores{}, nil)
	os.Exit(m.Run())
}

// ── Registry Tests ──────────────────────────

func TestRegistryHasTools(t *testing.T) {
	// TestMain registers note tools via RegisterNoteTools
	if !HasTools() {
		t.Fatal("Expected HasTools() == true after init registration")
	}
}

func TestRegistryAllDefinitions(t *testing.T) {
	defs := AllDefinitions()
	if len(defs) < 4 {
		t.Errorf("Expected at least 4 tool definitions, got %d", len(defs))
	}

	expected := map[string]bool{
		"note_create": false,
		"note_search": false,
		"note_update": false,
		"note_list":   false,
	}
	for _, d := range defs {
		if _, ok := expected[d.Name]; ok {
			expected[d.Name] = true
		}
		// Every tool must have a description and parameters
		if d.Description == "" {
			t.Errorf("Tool %q has empty description", d.Name)
		}
		if len(d.Parameters) == 0 {
			t.Errorf("Tool %q has empty parameters", d.Name)
		}
	}
	for name, found := range expected {
		if !found {
			t.Errorf("Expected tool %q not found in registry", name)
		}
	}
}

func TestRegistryGetKnown(t *testing.T) {
	tool := Get("note_create")
	if tool == nil {
		t.Fatal("Get(note_create) returned nil")
	}
	def := tool.Definition()
	if def.Name != "note_create" {
		t.Errorf("Expected name 'note_create', got %q", def.Name)
	}
}

func TestRegistryGetUnknown(t *testing.T) {
	tool := Get("nonexistent_tool")
	if tool != nil {
		t.Error("Get(nonexistent_tool) should return nil")
	}
}

func TestExecuteCallUnknownTool(t *testing.T) {
	ctx := context.Background()
	execCtx := ExecutionContext{UserID: "test", ChannelID: "test"}
	call := ToolCall{ID: "call_1", Name: "nonexistent", Arguments: "{}"}

	result := ExecuteCall(ctx, execCtx, call)

	if !result.IsError {
		t.Error("Expected is_error=true for unknown tool")
	}
	if result.ToolCallID != "call_1" {
		t.Errorf("Expected tool_call_id='call_1', got %q", result.ToolCallID)
	}
}

func TestExecuteAll(t *testing.T) {
	ctx := context.Background()
	execCtx := ExecutionContext{UserID: "test", ChannelID: "test"}
	calls := []ToolCall{
		{ID: "call_1", Name: "nonexistent_a", Arguments: "{}"},
		{ID: "call_2", Name: "nonexistent_b", Arguments: "{}"},
	}

	results := ExecuteAll(ctx, execCtx, calls)

	if len(results) != 2 {
		t.Fatalf("Expected 2 results, got %d", len(results))
	}
	for i, r := range results {
		if !r.IsError {
			t.Errorf("Result %d: expected is_error=true", i)
		}
	}
}

// ── Tool Definition Schema Tests ────────────

func TestToolDefinitionsHaveValidJSON(t *testing.T) {
	for _, def := range AllDefinitions() {
		var schema map[string]interface{}
		if err := json.Unmarshal(def.Parameters, &schema); err != nil {
			t.Errorf("Tool %q has invalid JSON schema: %v", def.Name, err)
		}
		// Should be a JSON Schema object with "type" and "properties"
		if schema["type"] != "object" {
			t.Errorf("Tool %q schema type should be 'object', got %v", def.Name, schema["type"])
		}
		if _, ok := schema["properties"]; !ok {
			t.Errorf("Tool %q schema missing 'properties'", def.Name)
		}
	}
}

// ── JSON Schema Helper Tests ────────────────

func TestPropString(t *testing.T) {
	p := Prop("string", "a description")

	if p["type"] != "string" {
		t.Errorf("Expected type=string, got %v", p["type"])
	}
	if p["description"] != "a description" {
		t.Errorf("Expected description, got %v", p["description"])
	}
}

func TestPropEnum(t *testing.T) {
	p := PropEnum("content mode", "replace", "append", "prepend")

	if p["type"] != "string" {
		t.Error("Expected type=string")
	}
	enums := p["enum"].([]string)
	if len(enums) != 3 {
		t.Errorf("Expected 3 enum values, got %d", len(enums))
	}
}

func TestPropArray(t *testing.T) {
	p := PropArray("list of tags")

	if p["type"] != "array" {
		t.Error("Expected type=array")
	}
	items := p["items"].(map[string]string)
	if items["type"] != "string" {
		t.Error("Expected items.type=string")
	}
}

func TestJSONSchema(t *testing.T) {
	props := map[string]interface{}{
		"title":   Prop("string", "note title"),
		"content": Prop("string", "note body"),
	}
	schema := JSONSchema(props, []string{"title"})

	var m map[string]interface{}
	if err := json.Unmarshal(schema, &m); err != nil {
		t.Fatalf("Invalid JSON: %v", err)
	}

	if m["type"] != "object" {
		t.Error("Expected type=object")
	}
	required := m["required"].([]interface{})
	if len(required) != 1 || required[0] != "title" {
		t.Errorf("Expected required=[title], got %v", required)
	}
	mProps := m["properties"].(map[string]interface{})
	if _, ok := mProps["title"]; !ok {
		t.Error("Missing property 'title'")
	}
	if _, ok := mProps["content"]; !ok {
		t.Error("Missing property 'content'")
	}
}

func TestMustJSON(t *testing.T) {
	result := MustJSON(map[string]string{"key": "value"})
	var m map[string]string
	if err := json.Unmarshal(result, &m); err != nil {
		t.Fatalf("MustJSON produced invalid JSON: %v", err)
	}
	if m["key"] != "value" {
		t.Errorf("Expected value, got %q", m["key"])
	}
}
