package core

import (
	"context"
	"testing"
)

// === Test struct for schema generation ===

type weatherArgs struct {
	City    string  `json:"city" description:"The city name" required:"true"`
	Country string  `json:"country,omitempty" description:"Optional country name"`
	Temp    float64 `json:"temp" description:"Temperature" required:"true"`
	Units   string  `json:"units" enum:"metric,imperial" description:"Temperature units"`
}

type emptyArgs struct{}

type nestedArgs struct {
	Query string   `json:"query" description:"Search query"`
	Page  int      `json:"page" description:"Page number"`
	Tags  []string `json:"tags" description:"Filter tags"`
}

// ======================== GenerateToolInfo ========================

func TestGenerateToolInfo_Basic(t *testing.T) {
	fn := func(ctx context.Context, args *weatherArgs) (string, error) {
		return "sunny", nil
	}

	info, err := GenerateToolInfo[weatherArgs]("get_weather", "Get current weather", fn)
	if err != nil {
		t.Fatalf("GenerateToolInfo: %v", err)
	}
	if info.Name != "get_weather" {
		t.Errorf("expected 'get_weather', got %s", info.Name)
	}
	if info.Description != "Get current weather" {
		t.Errorf("expected 'Get current weather', got %s", info.Description)
	}
	if info.InputSchema == nil {
		t.Fatal("expected non-nil InputSchema")
	}

	props, ok := info.InputSchema.(map[string]interface{})["properties"].(map[string]interface{})
	if !ok {
		t.Fatal("expected properties map")
	}

	// Verify each field exists with correct type
	cityProp, ok := props["city"].(map[string]interface{})
	if !ok {
		t.Fatal("expected city property schema")
	}
	if cityProp["type"] != "string" {
		t.Errorf("expected city type 'string', got %v", cityProp["type"])
	}
	if cityProp["description"] != "The city name" {
		t.Errorf("expected city description 'The city name', got %v", cityProp["description"])
	}

	tempProp, ok := props["temp"].(map[string]interface{})
	if !ok {
		t.Fatal("expected temp property schema")
	}
	if tempProp["type"] != "number" {
		t.Errorf("expected temp type 'number', got %v", tempProp["type"])
	}

	unitsProp, ok := props["units"].(map[string]interface{})
	if !ok {
		t.Fatal("expected units property schema")
	}
	if unitsProp["type"] != "string" {
		t.Errorf("expected units type 'string', got %v", unitsProp["type"])
	}
}

func TestGenerateToolInfo_EmptyStruct(t *testing.T) {
	fn := func(ctx context.Context, args *emptyArgs) (string, error) {
		return "no args", nil
	}

	info, err := GenerateToolInfo[emptyArgs]("noop", "Does nothing", fn)
	if err != nil {
		t.Fatalf("GenerateToolInfo: %v", err)
	}
	if info.Name != "noop" {
		t.Errorf("expected 'noop', got %s", info.Name)
	}
	props := info.InputSchema.(map[string]interface{})["properties"].(map[string]interface{})
	if len(props) != 0 {
		t.Errorf("expected 0 properties, got %d", len(props))
	}
}

func TestGenerateToolInfo_NestedTypes(t *testing.T) {
	fn := func(ctx context.Context, args *nestedArgs) (string, error) {
		return "nested", nil
	}

	info, err := GenerateToolInfo[nestedArgs]("search", "Search", fn)
	if err != nil {
		t.Fatalf("GenerateToolInfo: %v", err)
	}

	props := info.InputSchema.(map[string]interface{})["properties"].(map[string]interface{})
	pageProp := props["page"].(map[string]interface{})
	if pageProp["type"] != "integer" {
		t.Errorf("expected page type 'integer', got %v", pageProp["type"])
	}

	tagsProp := props["tags"].(map[string]interface{})
	if tagsProp["type"] != "array" {
		t.Errorf("expected tags type 'array', got %v", tagsProp["type"])
	}
}

func TestGenerateToolInfo_PointerStruct(t *testing.T) {
	fn := func(ctx context.Context, args *weatherArgs) (string, error) { return "ok", nil }

	info, err := GenerateToolInfo[*weatherArgs]("ptr_test", "test", fn)
	if err != nil {
		t.Fatalf("GenerateToolInfo pointer: %v", err)
	}
	if info.Name != "ptr_test" {
		t.Errorf("expected 'ptr_test', got %s", info.Name)
	}
}

// ======================== ReflectTool ========================

func TestReflectTool_Basic(t *testing.T) {
	tool, err := ReflectTool("greet", "Greet someone",
		func(ctx context.Context, args *weatherArgs) (string, error) {
			return "Hello " + args.City, nil
		})
	if err != nil {
		t.Fatalf("ReflectTool: %v", err)
	}
	if tool.Name() != "greet" {
		t.Errorf("expected 'greet', got %s", tool.Name())
	}
	if tool.Description() != "Greet someone" {
		t.Errorf("expected 'Greet someone', got %s", tool.Description())
	}

	result, err := tool.Invoke(context.Background(), `{"city":"London"}`)
	if err != nil {
		t.Fatalf("Invoke: %v", err)
	}
	if result != "Hello London" {
		t.Errorf("expected 'Hello London', got %s", result)
	}
}

func TestReflectTool_Stream(t *testing.T) {
	tool, err := ReflectTool("echo", "Echo",
		func(ctx context.Context, args *weatherArgs) (string, error) {
			return "echo: " + args.City, nil
		})
	if err != nil {
		t.Fatalf("ReflectTool: %v", err)
	}

	stream, err := tool.Stream(context.Background(), `{"city":"test"}`)
	if err != nil {
		t.Fatalf("Stream: %v", err)
	}
	if stream == nil {
		t.Fatal("expected non-nil stream")
	}
}

func TestReflectTool_ToolInfo(t *testing.T) {
	tool, err := ReflectTool("info_test", "Info test",
		func(ctx context.Context, args *weatherArgs) (string, error) {
			return "info", nil
		})
	if err != nil {
		t.Fatalf("ReflectTool: %v", err)
	}

	info := tool.ToolInfo()
	if info.Name != "info_test" {
		t.Errorf("expected 'info_test', got %s", info.Name)
	}
}

func TestReflectTool_InvalidJSON(t *testing.T) {
	tool, err := ReflectTool("bad_json", "Bad JSON",
		func(ctx context.Context, args *weatherArgs) (string, error) {
			return "ok", nil
		})
	if err != nil {
		t.Fatalf("ReflectTool: %v", err)
	}

	_, err = tool.Invoke(context.Background(), `not valid json`)
	if err == nil {
		t.Error("expected error for invalid JSON")
	}
}

func TestMustReflectTool(t *testing.T) {
	tool := MustReflectTool("must", "Must tool",
		func(ctx context.Context, args *weatherArgs) (string, error) {
			return "must ok", nil
		})
	if tool.Name() != "must" {
		t.Errorf("expected 'must', got %s", tool.Name())
	}
}

func TestReflectTool_RegistryIntegration(t *testing.T) {
	r := NewToolRegistry()
	tool := MustReflectTool("registry_test", "Registry test",
		func(ctx context.Context, args *weatherArgs) (string, error) {
			return "registry ok", nil
		})
	r.Register(tool, WithCategory("test"))

	found, ok := r.Lookup("registry_test")
	if !ok {
		t.Fatal("expected to find tool in registry")
	}
	if found.Name() != "registry_test" {
		t.Errorf("expected 'registry_test', got %s", found.Name())
	}
}
