package mcp

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

	"github.com/dtoro/oikos/internal/secrets"
	"github.com/google/uuid"
	"github.com/modelcontextprotocol/go-sdk/mcp"
)

type mockSecretBackend struct {
	data map[string]string
}

func (m *mockSecretBackend) Get(ctx context.Context, key string) (string, error) {
	v, ok := m.data[key]
	if !ok {
		return "", secrets.ErrNotFound
	}
	return v, nil
}

func (m *mockSecretBackend) Set(ctx context.Context, key string, value string) error {
	m.data[key] = value
	return nil
}

func (m *mockSecretBackend) List(ctx context.Context) ([]string, error) {
	keys := make([]string, 0, len(m.data))
	for k := range m.data {
		keys = append(keys, k)
	}
	return keys, nil
}

func (m *mockSecretBackend) Name() string { return "mock" }

// findToolHandler locates a tool's handler from allTools by name.
func findToolHandler(t *testing.T, pool interface{}, name string, sec secrets.Backend) func(context.Context, *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	t.Helper()
	for _, r := range allTools(nil, uuid.Nil, sec, nil, nil) {
		if r.tool.Name == name {
			return r.handler
		}
	}
	t.Fatalf("tool %q not found", name)
	return nil
}

func callToolJSON(t *testing.T, name string, sec secrets.Backend, args map[string]any) any {
	t.Helper()
	handler := findToolHandler(t, nil, name, sec)
	argBytes, _ := json.Marshal(args)
	req := &mcp.CallToolRequest{
		Params: &mcp.CallToolParamsRaw{Arguments: argBytes},
	}
	result, err := handler(context.Background(), req)
	if err != nil {
		t.Fatalf("tool %q error: %v", name, err)
	}
	if len(result.Content) == 0 {
		t.Fatalf("tool %q returned no content", name)
	}
	tc := result.Content[0].(*mcp.TextContent)
	var out any
	if err := json.Unmarshal([]byte(tc.Text), &out); err != nil {
		// Not JSON — return raw string
		return tc.Text
	}
	return out
}

func callToolText(t *testing.T, name string, sec secrets.Backend, args map[string]any) string {
	t.Helper()
	handler := findToolHandler(t, nil, name, sec)
	argBytes, _ := json.Marshal(args)
	req := &mcp.CallToolRequest{
		Params: &mcp.CallToolParamsRaw{Arguments: argBytes},
	}
	result, err := handler(context.Background(), req)
	if err != nil {
		t.Fatalf("tool %q error: %v", name, err)
	}
	if len(result.Content) == 0 {
		t.Fatalf("tool %q returned no content", name)
	}
	return result.Content[0].(*mcp.TextContent).Text
}

func TestGetSecret(t *testing.T) {
	sec := &mockSecretBackend{
		data: map[string]string{
			"matrix-token":  "bot-token-123",
			"clients/host:hubris/age-key": "AGE-SECRET-KEY",
		},
	}

	// Get existing key
	val := callToolText(t, "get_secret", sec, map[string]any{"key": "matrix-token"})
	if val != "bot-token-123" {
		t.Errorf("get_secret = %q, want bot-token-123", val)
	}

	// Get missing key
	errText := callToolText(t, "get_secret", sec, map[string]any{"key": "nonexistent"})
	if errText == "" {
		t.Error("expected error for missing key")
	}

	// Missing key arg
	errText = callToolText(t, "get_secret", sec, map[string]any{})
	if errText != "error: key is required" {
		t.Errorf("missing key error = %q, want error: key is required", errText)
	}
}

func TestListSecrets(t *testing.T) {
	sec := &mockSecretBackend{
		data: map[string]string{
			"clients/host:hubris/age-key": "val1",
			"clients/host:strong/age-key": "val2",
			"shared/matrix-token":          "val3",
		},
	}

	// List all
	out := callToolJSON(t, "list_secrets", sec, map[string]any{})
	keys, ok := out.([]any)
	if !ok {
		t.Fatalf("list_secrets returned non-array: %T", out)
	}
	if len(keys) != 3 {
		t.Errorf("list_secrets count = %d, want 3", len(keys))
	}

	// List with prefix filter
	out = callToolJSON(t, "list_secrets", sec, map[string]any{"path_prefix": "clients/"})
	keys, ok = out.([]any)
	if !ok {
		t.Fatalf("filtered list returned non-array: %T", out)
	}
	if len(keys) != 2 {
		t.Errorf("filtered list count = %d, want 2", len(keys))
	}
}

func TestSetSecret(t *testing.T) {
	sec := &mockSecretBackend{
		data: map[string]string{},
	}

	// Set a key
	result := callToolText(t, "set_secret", sec, map[string]any{"key": "test-key", "value": "test-value"})
	if result != "secret test-key stored" {
		t.Errorf("set_secret = %q, want 'secret test-key stored'", result)
	}

	// Verify it was stored
	val, err := sec.Get(context.Background(), "test-key")
	if err != nil {
		t.Fatalf("verify get: %v", err)
	}
	if val != "test-value" {
		t.Errorf("stored value = %q, want test-value", val)
	}

	// Missing key
	errText := callToolText(t, "set_secret", sec, map[string]any{})
	if errText != "error: key is required" {
		t.Errorf("missing key error = %q", errText)
	}

	// Missing value
	errText = callToolText(t, "set_secret", sec, map[string]any{"key": "x"})
	if errText != "error: value is required" {
		t.Errorf("missing value error = %q", errText)
	}
}

func TestSecretToolsNilBackend(t *testing.T) {
	// All tools should return a graceful error when no backend is configured
	errText := callToolText(t, "get_secret", nil, map[string]any{"key": "x"})
	if errText != "error: no secrets backend configured (set OIKOS_INFISICAL_SITE_URL)" {
		t.Errorf("nil backend get_secret = %q", errText)
	}

	errText = callToolText(t, "list_secrets", nil, map[string]any{})
	if errText != "error: no secrets backend configured (set OIKOS_INFISICAL_SITE_URL)" {
		t.Errorf("nil backend list_secrets = %q", errText)
	}

	errText = callToolText(t, "set_secret", nil, map[string]any{"key": "x", "value": "y"})
	if errText != "error: no secrets backend configured (set OIKOS_INFISICAL_SITE_URL)" {
		t.Errorf("nil backend set_secret = %q", errText)
	}
}
