package cmd

import (
	"net/http"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/tidbcloud/clinic-cli/internal/config"
)

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

	client := &recordingClinicClient{
		responses: []clinicResponse{
			{body: []byte(`{"items":[{"clusterID":"branch-cluster-7","orgID":"org-branch"},{"clusterID":"cluster-7","orgID":"org-actual"}]}`)},
			{body: []byte(`{"items":[{"id":"evt-1"}]}`)},
		},
	}
	ctx := &RuntimeContext{Client: client}

	var stdout strings.Builder
	if err := eventsCommandHandler(ctx, []string{
		"list",
		"--cluster-id", "cluster-7",
		"--start", "1712123000",
		"--end", "1712123600",
	}, &stdout); err != nil {
		t.Fatalf("eventsCommandHandler() error = %v", err)
	}

	if got, want := len(client.requests), 2; got != want {
		t.Fatalf("request count = %d, want %d", got, want)
	}
	assertQueryValue(t, client.requests[0].Query, "cluster_id", "cluster-7")
	assertQueryValue(t, client.requests[0].Query, "show_deleted", "true")
	if got, want := client.requests[1].Method, http.MethodGet; got != want {
		t.Fatalf("method = %q, want %q", got, want)
	}
	if got, want := client.requests[1].Path, "/clinic/api/v1/activityhub/applications/org-actual/targets/cluster-7/activities"; got != want {
		t.Fatalf("path = %q, want %q", got, want)
	}
	assertJSONEqual(t, stdout.String(), `{"items":[{"id":"evt-1"}]}`)
}

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

	client := &recordingClinicClient{
		responses: []clinicResponse{
			{body: []byte(`{"items":[{"id":"evt-cache"}]}`)},
		},
	}
	ctx := &RuntimeContext{
		Client:      client,
		SelectedEnv: "staging",
		SelectedProfile: &config.ProfileRecord{
			ClusterID: "cluster-7",
			OrgID:     "org-actual",
		},
	}

	var stdout strings.Builder
	if err := eventsCommandHandler(ctx, []string{
		"list",
		"--cluster-id", "cluster-7",
		"--start", "1712123000",
		"--end", "1712123600",
	}, &stdout); err != nil {
		t.Fatalf("eventsCommandHandler() error = %v", err)
	}

	if got, want := len(client.requests), 1; got != want {
		t.Fatalf("request count = %d, want %d", got, want)
	}
	if got, want := client.requests[0].Path, "/clinic/api/v1/activityhub/applications/org-actual/targets/cluster-7/activities"; got != want {
		t.Fatalf("path = %q, want %q", got, want)
	}
	assertJSONEqual(t, stdout.String(), `{"items":[{"id":"evt-cache"}]}`)
}

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

	client := &recordingClinicClient{
		responses: []clinicResponse{
			{body: []byte(`{"items":[{"clusterID":"branch-cluster-7","orgID":"org-branch"},{"clusterID":"cluster-7","orgID":"org-actual"}]}`)},
			{body: []byte(`{"items":[{"id":"evt-miss"}]}`)},
		},
	}
	ctx := &RuntimeContext{
		Client:      client,
		SelectedEnv: "staging",
		SelectedProfile: &config.ProfileRecord{
			ClusterID: "cluster-other",
			OrgID:     "org-wrong",
		},
	}

	var stdout strings.Builder
	if err := eventsCommandHandler(ctx, []string{
		"list",
		"--cluster-id", "cluster-7",
		"--start", "1712123000",
		"--end", "1712123600",
	}, &stdout); err != nil {
		t.Fatalf("eventsCommandHandler() error = %v", err)
	}

	if got, want := len(client.requests), 2; got != want {
		t.Fatalf("request count = %d, want %d", got, want)
	}
	assertQueryValue(t, client.requests[0].Query, "cluster_id", "cluster-7")
	if got, want := client.requests[1].Path, "/clinic/api/v1/activityhub/applications/org-actual/targets/cluster-7/activities"; got != want {
		t.Fatalf("path = %q, want %q", got, want)
	}
	assertJSONEqual(t, stdout.String(), `{"items":[{"id":"evt-miss"}]}`)
}

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

	client := &recordingClinicClient{
		responses: []clinicResponse{
			{body: []byte(`{"items":[{"clusterID":"cluster-7","orgID":"org-actual"}]}`)},
			{body: []byte(`{"items":[{"id":"evt-2"}]}`)},
		},
	}
	ctx := &RuntimeContext{Client: client}

	var stdout strings.Builder
	if err := eventsCommandHandler(ctx, []string{
		"list",
		"--cluster-id", "cluster-7",
		"--start", "1712123000",
		"--end", "1712123600",
		"--name", "SCALE_OUT",
		"--severity", "2",
		"--limit", "50",
		"--cursor", "cursor-123",
	}, &stdout); err != nil {
		t.Fatalf("eventsCommandHandler() error = %v", err)
	}

	if got, want := len(client.requests), 2; got != want {
		t.Fatalf("request count = %d, want %d", got, want)
	}
	request := client.requests[1]
	assertQueryValue(t, request.Query, "begin_ts", "1712123000")
	assertQueryValue(t, request.Query, "end_ts", "1712123600")
	assertQueryValue(t, request.Query, "name", "SCALE_OUT")
	assertQueryValue(t, request.Query, "severity", "2")
	assertQueryValue(t, request.Query, "limit", "50")
	if got, want := request.Headers.Get("Cursor"), "cursor-123"; got != want {
		t.Fatalf("Cursor header = %q, want %q", got, want)
	}
	assertJSONEqual(t, stdout.String(), `{"items":[{"id":"evt-2"}]}`)
}

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

	testCases := []struct {
		name         string
		args         []string
		wantSeverity string
		wantLimit    string
		wantError    string
	}{
		{
			name: "severity and limit omitted are not sent",
			args: []string{
				"list",
				"--cluster-id", "cluster-7",
				"--start", "1712123000",
				"--end", "1712123600",
			},
		},
		{
			name: "severity zero is sent when explicitly provided",
			args: []string{
				"list",
				"--cluster-id", "cluster-7",
				"--start", "1712123000",
				"--end", "1712123600",
				"--severity", "0",
				"--limit", "5",
			},
			wantSeverity: "0",
			wantLimit:    "5",
		},
		{
			name: "limit zero is rejected when explicitly provided",
			args: []string{
				"list",
				"--cluster-id", "cluster-7",
				"--start", "1712123000",
				"--end", "1712123600",
				"--limit", "0",
			},
			wantError: "limit must be greater than 0",
		},
	}

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

			client := &recordingClinicClient{
				responses: []clinicResponse{
					{body: []byte(`{"items":[{"clusterID":"cluster-7","orgID":"org-actual"}]}`)},
					{body: []byte(`{"items":[{"id":"evt-changed"}]}`)},
				},
			}
			ctx := &RuntimeContext{Client: client}

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

			if got, want := len(client.requests), 2; got != want {
				t.Fatalf("request count = %d, want %d", got, want)
			}
			request := client.requests[1]
			if got := request.Query.Get("severity"); got != tc.wantSeverity {
				t.Fatalf("severity query value = %q, want %q", got, tc.wantSeverity)
			}
			if got := request.Query.Get("limit"); got != tc.wantLimit {
				t.Fatalf("limit query value = %q, want %q", got, tc.wantLimit)
			}
		})
	}
}

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

	client := &recordingClinicClient{
		responses: []clinicResponse{
			{body: []byte(`{"items":[{"clusterID":"cluster-7","orgID":"org-actual"}]}`)},
			{body: []byte(`{"items":[{"id":"evt-3"}]}`)},
		},
	}
	ctx := &RuntimeContext{
		Client: client,
		SelectedProfile: &config.ProfileRecord{
			ClusterID:  "cluster-7",
			RangeStart: "2026-04-03 10:04:43",
			RangeEnd:   "1712123600000",
		},
	}

	var stdout strings.Builder
	if err := eventsCommandHandler(ctx, []string{"list"}, &stdout); err != nil {
		t.Fatalf("eventsCommandHandler() error = %v", err)
	}

	if got, want := len(client.requests), 2; got != want {
		t.Fatalf("request count = %d, want %d", got, want)
	}
	assertQueryValue(t, client.requests[0].Query, "cluster_id", "cluster-7")
	assertQueryValue(t, client.requests[1].Query, "begin_ts", strconv.FormatInt(time.Date(2026, time.April, 3, 10, 4, 43, 0, time.Local).Unix(), 10))
	assertQueryValue(t, client.requests[1].Query, "end_ts", "1712123600")
	if got, want := client.requests[1].Path, "/clinic/api/v1/activityhub/applications/org-actual/targets/cluster-7/activities"; got != want {
		t.Fatalf("path = %q, want %q", got, want)
	}
	assertJSONEqual(t, stdout.String(), `{"items":[{"id":"evt-3"}]}`)
}

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

	client := &recordingClinicClient{
		responses: []clinicResponse{
			{body: []byte(`{"items":[{"clusterID":"cluster-7","orgID":"org-actual"}]}`)},
			{body: []byte(`{"items":[{"id":"evt-latest"}]}`)},
		},
	}
	ctx := &RuntimeContext{Client: client}

	before := time.Now().Unix()
	var stdout strings.Builder
	if err := eventsCommandHandler(ctx, []string{
		"list",
		"--cluster-id", "cluster-7",
	}, &stdout); err != nil {
		t.Fatalf("eventsCommandHandler() error = %v", err)
	}
	after := time.Now().Unix()

	if got, want := len(client.requests), 2; got != want {
		t.Fatalf("request count = %d, want %d", got, want)
	}
	beginRaw := client.requests[1].Query.Get("begin_ts")
	endRaw := client.requests[1].Query.Get("end_ts")
	beginUnix, err := strconv.ParseInt(beginRaw, 10, 64)
	if err != nil {
		t.Fatalf("begin_ts query value = %q, want unix seconds: %v", beginRaw, err)
	}
	endUnix, err := strconv.ParseInt(endRaw, 10, 64)
	if err != nil {
		t.Fatalf("end_ts query value = %q, want unix seconds: %v", endRaw, err)
	}
	if got, want := endUnix-beginUnix, int64(7*24*3600); got != want {
		t.Fatalf("range length = %d, want %d", got, want)
	}
	if endUnix < before || endUnix > after {
		t.Fatalf("end_ts query value = %d, want between %d and %d", endUnix, before, after)
	}
	assertJSONEqual(t, stdout.String(), `{"items":[{"id":"evt-latest"}]}`)
}
