package tool

import (
	"context"
	"slices"
	"testing"
	"time"
)

func TestToolValidate(t *testing.T) {
	tests := []struct {
		name    string
		tool    Tool
		wantErr bool
	}{
		{
			name: "valid tool",
			tool: Tool{
				Name:        "echo",
				Description: "echoes input",
				Command:     "echo",
				Arguments:   []string{"{{.message}}"},
			},
			wantErr: false,
		},
		{
			name: "valid tool with timeout",
			tool: Tool{
				Name:        "slow",
				Description: "slow command",
				Command:     "sleep",
				Arguments:   []string{"1"},
				Timeout:     "5s",
			},
			wantErr: false,
		},
		{
			name: "missing name",
			tool: Tool{
				Description: "test",
				Command:     "echo",
			},
			wantErr: true,
		},
		{
			name: "missing description",
			tool: Tool{
				Name:    "test",
				Command: "echo",
			},
			wantErr: true,
		},
		{
			name: "missing command",
			tool: Tool{
				Name:        "test",
				Description: "test",
			},
			wantErr: true,
		},
		{
			name: "invalid timeout",
			tool: Tool{
				Name:        "test",
				Description: "test",
				Command:     "echo",
				Timeout:     "invalid",
			},
			wantErr: true,
		},
		{
			name: "invalid template",
			tool: Tool{
				Name:        "test",
				Description: "test",
				Command:     "echo",
				Arguments:   []string{"{{.unclosed"},
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := tt.tool.Validate()
			if (err != nil) != tt.wantErr {
				t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestToolParseArguments(t *testing.T) {
	tests := []struct {
		name    string
		tool    Tool
		args    string
		want    []string
		wantErr bool
	}{
		{
			name: "simple substitution",
			tool: Tool{
				Arguments: []string{"{{.message}}"},
			},
			args: `{"message": "hello"}`,
			want: []string{"hello"},
		},
		{
			name: "multiple arguments",
			tool: Tool{
				Arguments: []string{"-n", "{{.count}}", "{{.file}}"},
			},
			args: `{"count": "10", "file": "test.txt"}`,
			want: []string{"-n", "10", "test.txt"},
		},
		{
			name: "conditional with if",
			tool: Tool{
				Arguments: []string{"{{.required}}", "{{if .optional}}{{.optional}}{{end}}"},
			},
			args: `{"required": "value"}`,
			want: []string{"value"},
		},
		{
			name: "json function",
			tool: Tool{
				Arguments: []string{`{{json .data}}`},
			},
			args: `{"data": {"key": "value"}}`,
			want: []string{`{"key":"value"}`},
		},
		{
			name: "empty JSON object",
			tool: Tool{
				Arguments: []string{"fixed"},
			},
			args: "{}",
			want: []string{"fixed"},
		},
		{
			name: "empty string args",
			tool: Tool{
				Arguments: []string{"fixed"},
			},
			args: "",
			want: []string{"fixed"},
		},
		{
			name: "invalid JSON",
			tool: Tool{
				Arguments: []string{"{{.x}}"},
			},
			args:    "not json",
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := tt.tool.ParseArguments(tt.args)
			if (err != nil) != tt.wantErr {
				t.Errorf("ParseArguments() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !tt.wantErr && !slices.Equal(got, tt.want) {
				t.Errorf("ParseArguments() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestNewRegistry(t *testing.T) {
	tests := []struct {
		name    string
		tools   []Tool
		wantErr bool
	}{
		{
			name: "valid registry",
			tools: []Tool{
				{Name: "a", Description: "a", Command: "echo"},
				{Name: "b", Description: "b", Command: "cat"},
			},
			wantErr: false,
		},
		{
			name:    "empty registry",
			tools:   []Tool{},
			wantErr: false,
		},
		{
			name: "duplicate names",
			tools: []Tool{
				{Name: "dup", Description: "first", Command: "echo"},
				{Name: "dup", Description: "second", Command: "cat"},
			},
			wantErr: true,
		},
		{
			name: "invalid tool",
			tools: []Tool{
				{Name: "", Description: "missing name", Command: "echo"},
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := NewRegistry(tt.tools)
			if (err != nil) != tt.wantErr {
				t.Errorf("NewRegistry() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestRegistryGet(t *testing.T) {
	r, err := NewRegistry([]Tool{
		{Name: "echo", Description: "echo", Command: "echo"},
	})
	if err != nil {
		t.Fatalf("NewRegistry() error = %v", err)
	}

	tool, ok := r.Get("echo")
	if !ok {
		t.Error("Get(echo) returned false, want true")
	}
	if tool.Name != "echo" {
		t.Errorf("Get(echo).Name = %q, want %q", tool.Name, "echo")
	}

	_, ok = r.Get("nonexistent")
	if ok {
		t.Error("Get(nonexistent) returned true, want false")
	}
}

func TestRegistryList(t *testing.T) {
	r, err := NewRegistry([]Tool{
		{Name: "a", Description: "a", Command: "echo"},
		{Name: "b", Description: "b", Command: "cat"},
		{Name: "c", Description: "c", Command: "ls"},
	})
	if err != nil {
		t.Fatalf("NewRegistry() error = %v", err)
	}

	names := r.List()
	if len(names) != 3 {
		t.Errorf("List() returned %d names, want 3", len(names))
	}

	slices.Sort(names)
	want := []string{"a", "b", "c"}
	if !slices.Equal(names, want) {
		t.Errorf("List() = %v, want %v", names, want)
	}
}

func TestRegistryExecute(t *testing.T) {
	r, err := NewRegistry([]Tool{
		{
			Name:        "echo",
			Description: "echo message",
			Command:     "echo",
			Arguments:   []string{"{{.message}}"},
		},
		{
			Name:        "cat",
			Description: "cat stdin",
			Command:     "cat",
			Arguments:   []string{},
		},
	})
	if err != nil {
		t.Fatalf("NewRegistry() error = %v", err)
	}

	ctx := context.Background()

	// Successful execution.
	out, err := r.Execute(ctx, "echo", `{"message": "hello world"}`)
	if err != nil {
		t.Errorf("Execute(echo) error = %v", err)
	}
	if out != "hello world\n" {
		t.Errorf("Execute(echo) = %q, want %q", out, "hello world\n")
	}

	// Unknown tool.
	_, err = r.Execute(ctx, "unknown", "{}")
	if err == nil {
		t.Error("Execute(unknown) expected error, got nil")
	}

	// Invalid JSON arguments.
	_, err = r.Execute(ctx, "echo", "not json")
	if err == nil {
		t.Error("Execute with invalid JSON expected error, got nil")
	}
}

func TestRegistryExecuteTimeout(t *testing.T) {
	r, err := NewRegistry([]Tool{
		{
			Name:        "slow",
			Description: "slow command",
			Command:     "sleep",
			Arguments:   []string{"10"},
			Timeout:     "50ms",
		},
	})
	if err != nil {
		t.Fatalf("NewRegistry() error = %v", err)
	}

	ctx := context.Background()
	start := time.Now()
	_, err = r.Execute(ctx, "slow", "{}")
	elapsed := time.Since(start)

	if err == nil {
		t.Error("Execute(slow) expected timeout error, got nil")
	}
	if elapsed > time.Second {
		t.Errorf("Execute(slow) took %v, expected ~50ms timeout", elapsed)
	}
}

func TestRegistryExecuteFailure(t *testing.T) {
	r, err := NewRegistry([]Tool{
		{
			Name:        "fail",
			Description: "always fails",
			Command:     "false",
		},
	})
	if err != nil {
		t.Fatalf("NewRegistry() error = %v", err)
	}

	_, err = r.Execute(context.Background(), "fail", "{}")
	if err == nil {
		t.Error("Execute(fail) expected error, got nil")
	}
}

func TestToolOpenAI(t *testing.T) {
	tool := Tool{
		Name:        "test",
		Description: "test tool",
		Command:     "echo",
		Parameters: map[string]any{
			"type": "object",
			"properties": map[string]any{
				"message": map[string]any{
					"type":        "string",
					"description": "the message",
				},
			},
		},
	}

	openaiTool := tool.OpenAI()
	if openaiTool.Function.Name != "test" {
		t.Errorf("OpenAI().Function.Name = %q, want %q", openaiTool.Function.Name, "test")
	}
	if openaiTool.Function.Description != "test tool" {
		t.Errorf("OpenAI().Function.Description = %q, want %q", openaiTool.Function.Description, "test tool")
	}
}
