package cmd

import (
	"net/http"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
)

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

	ctx := &RuntimeContext{Client: &recordingClinicClient{}}
	testCases := []struct {
		name string
		args []string
		want string
	}{
		{
			name: "missing method",
			args: []string{"request", "--path", "/clinic/api/v1/orgs"},
			want: "missing required --method",
		},
		{
			name: "missing path",
			args: []string{"request", "--method", "GET"},
			want: "missing required --path",
		},
		{
			name: "invalid method",
			args: []string{"request", "--method", "NOT_A_METHOD", "--path", "/clinic/api/v1/orgs"},
			want: "invalid method: NOT_A_METHOD",
		},
		{
			name: "path outside allowlist",
			args: []string{"request", "--method", "GET", "--path", "/api/v1/orgs"},
			want: "path must start with /clinic/api/ or /data-proxy/",
		},
	}

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

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

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

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

	var stdout strings.Builder
	if err := rawCommandHandler(ctx, []string{
		"request",
		"--method", "GET",
		"--path", "/clinic/api/v1/orgs",
		"--query", "state=active",
		"--query", "state=paused",
		"--query", "page=2",
		"--header", "X-Debug: first",
		"--header", "X-Debug: second",
		"--header", "X-Trace: abc123",
	}, &stdout); err != nil {
		t.Fatalf("rawCommandHandler() 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, "/clinic/api/v1/orgs"; got != want {
		t.Fatalf("path = %q, want %q", got, want)
	}
	states := client.request.Query["state"]
	if got, want := len(states), 2; got != want {
		t.Fatalf("state query count = %d, want %d", got, want)
	}
	if got, want := states[0], "active"; got != want {
		t.Fatalf("first state = %q, want %q", got, want)
	}
	if got, want := states[1], "paused"; got != want {
		t.Fatalf("second state = %q, want %q", got, want)
	}
	assertQueryValue(t, client.request.Query, "page", "2")
	debugHeaders := client.request.Headers.Values("X-Debug")
	if got, want := len(debugHeaders), 2; got != want {
		t.Fatalf("X-Debug count = %d, want %d", got, want)
	}
	if got, want := debugHeaders[0], "first"; got != want {
		t.Fatalf("first X-Debug = %q, want %q", got, want)
	}
	if got, want := debugHeaders[1], "second"; got != want {
		t.Fatalf("second X-Debug = %q, want %q", got, want)
	}
	if got, want := client.request.Headers.Get("X-Trace"), "abc123"; got != want {
		t.Fatalf("X-Trace = %q, want %q", got, want)
	}
	assertJSONEqual(t, stdout.String(), `{"ok":true}`)
}

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

	filePath := filepath.Join(t.TempDir(), "body.json")
	if err := os.WriteFile(filePath, []byte(`{"source":"file","items":[1,2]}`), 0o600); err != nil {
		t.Fatalf("os.WriteFile() error = %v", err)
	}

	testCases := []struct {
		name     string
		bodyArg  string
		wantBody map[string]any
	}{
		{
			name:    "inline json",
			bodyArg: `{"source":"inline","enabled":true}`,
			wantBody: map[string]any{
				"source":  "inline",
				"enabled": true,
			},
		},
		{
			name:    "json file",
			bodyArg: "@" + filePath,
			wantBody: map[string]any{
				"source": "file",
				"items":  []any{float64(1), float64(2)},
			},
		},
	}

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

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

			var stdout strings.Builder
			if err := rawCommandHandler(ctx, []string{
				"request",
				"--method", "POST",
				"--path", "/data-proxy/query",
				"--body", tc.bodyArg,
			}, &stdout); err != nil {
				t.Fatalf("rawCommandHandler() error = %v", err)
			}

			body, ok := client.request.Body.(map[string]any)
			if !ok {
				t.Fatalf("body type = %T, want map[string]any", client.request.Body)
			}
			assertStructuredEqual(t, body, tc.wantBody)
			assertJSONEqual(t, stdout.String(), `{"accepted":true}`)
		})
	}
}

func assertStructuredEqual(t *testing.T, got, want any) {
	t.Helper()
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("value = %#v, want %#v", got, want)
	}
}
