package cmd

import (
	"net/http"
	"strings"
	"testing"
)

func TestDataQueryRequiresClusterIDAndSQL(t *testing.T) {
	t.Parallel()

	ctx := &RuntimeContext{Client: &recordingClinicClient{}}
	testCases := []struct {
		name string
		args []string
		want string
	}{
		{
			name: "missing cluster id",
			args: []string{"query", "--sql", "select 1"},
			want: "missing required --cluster-id",
		},
		{
			name: "missing sql",
			args: []string{"query", "--cluster-id", "c1"},
			want: "missing required --sql",
		},
		{
			name: "non positive timeout",
			args: []string{"query", "--cluster-id", "c1", "--sql", "select 1", "--timeout", "0"},
			want: "timeout must be greater than 0",
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			err := dataCommandHandler(ctx, tc.args, &strings.Builder{})
			if err == nil {
				t.Fatal("dataCommandHandler() error = nil, want validation error")
			}
			if got := err.Error(); got != tc.want {
				t.Fatalf("error = %q, want %q", got, tc.want)
			}
		})
	}
}

func TestDataQueryPostsExpectedPayload(t *testing.T) {
	t.Parallel()

	client := &recordingClinicClient{responses: []clinicResponse{{body: []byte(`{"rows":[{"answer":42}]}`)}}}
	ctx := &RuntimeContext{Client: client}

	var stdout strings.Builder
	if err := dataCommandHandler(ctx, []string{
		"query",
		"--cluster-id", "cluster-42",
		"--sql", "select * from metrics where service = 'tidb'",
		"--timeout", "12",
	}, &stdout); err != nil {
		t.Fatalf("dataCommandHandler() error = %v", err)
	}

	if got, want := client.request.Method, http.MethodPost; got != want {
		t.Fatalf("method = %q, want %q", got, want)
	}
	if got, want := client.request.Path, "/data-proxy/query"; got != want {
		t.Fatalf("path = %q, want %q", got, want)
	}
	if encoded := client.request.Query.Encode(); encoded != "" {
		t.Fatalf("query = %q, want empty", encoded)
	}

	payload, ok := client.request.Body.(map[string]any)
	if !ok {
		t.Fatalf("body type = %T, want map[string]any", client.request.Body)
	}
	if got, want := payload["clusterId"], "cluster-42"; got != want {
		t.Fatalf("body.clusterId = %#v, want %#v", got, want)
	}
	if got, want := payload["sql"], "select * from metrics where service = 'tidb'"; got != want {
		t.Fatalf("body.sql = %#v, want %#v", got, want)
	}
	if got, want := payload["timeout"], 12; got != want {
		t.Fatalf("body.timeout = %#v, want %#v", got, want)
	}
	assertJSONEqual(t, stdout.String(), `{"rows":[{"answer":42}]}`)
}

func TestDataTimeoutChangedDetectionWithCobra(t *testing.T) {
	t.Parallel()

	testCases := []struct {
		name             string
		args             []string
		wantHasTimeout   bool
		wantTimeoutValue int
	}{
		{
			name: "timeout omitted does not set request field",
			args: []string{
				"query",
				"--cluster-id", "cluster-42",
				"--sql", "select 42",
			},
			wantHasTimeout: false,
		},
		{
			name: "timeout provided sets request field",
			args: []string{
				"query",
				"--cluster-id", "cluster-42",
				"--sql", "select 42",
				"--timeout", "17",
			},
			wantHasTimeout:   true,
			wantTimeoutValue: 17,
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			client := &recordingClinicClient{responses: []clinicResponse{{body: []byte(`{"rows":[{"answer":42}]}`)}}}
			ctx := &RuntimeContext{Client: client}

			if err := dataCommandHandler(ctx, tc.args, &strings.Builder{}); err != nil {
				t.Fatalf("dataCommandHandler() error = %v", err)
			}

			payload, ok := client.request.Body.(map[string]any)
			if !ok {
				t.Fatalf("body type = %T, want map[string]any", client.request.Body)
			}

			timeoutValue, hasTimeout := payload["timeout"]
			if hasTimeout != tc.wantHasTimeout {
				t.Fatalf("body has timeout = %t, want %t", hasTimeout, tc.wantHasTimeout)
			}
			if tc.wantHasTimeout {
				if got, want := timeoutValue, tc.wantTimeoutValue; got != want {
					t.Fatalf("body.timeout = %#v, want %#v", got, want)
				}
			}
		})
	}
}

func TestDataSchemaUsesExpectedQueryParams(t *testing.T) {
	t.Parallel()

	client := &recordingClinicClient{responses: []clinicResponse{{body: []byte(`{"tables":[{"name":"orders"}]}`)}}}
	ctx := &RuntimeContext{Client: client}

	var stdout strings.Builder
	if err := dataCommandHandler(ctx, []string{
		"schema",
		"--cluster-id", "cluster-42",
		"--tables", "orders,customers",
	}, &stdout); err != nil {
		t.Fatalf("dataCommandHandler() error = %v", err)
	}

	if got, want := client.request.Method, http.MethodGet; got != want {
		t.Fatalf("method = %q, want %q", got, want)
	}
	if got, want := client.request.Path, "/data-proxy/schema"; got != want {
		t.Fatalf("path = %q, want %q", got, want)
	}
	assertQueryValue(t, client.request.Query, "clusterId", "cluster-42")
	assertQueryValue(t, client.request.Query, "tables", "orders,customers")
	if client.request.Body != nil {
		t.Fatalf("body = %#v, want nil", client.request.Body)
	}
	assertJSONEqual(t, stdout.String(), `{"tables":[{"name":"orders"}]}`)
}
