package gcp

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/require"
	"google.golang.org/api/container/v1"
	"google.golang.org/api/option"
)

func TestCloneNodePoolNodeTaintsFiltersNilAndClones(t *testing.T) {
	original := &container.NodeTaint{
		Key:    "dedicated",
		Value:  "tidb-shared",
		Effect: "NO_SCHEDULE",
	}

	got := cloneNodePoolNodeTaints([]*container.NodeTaint{nil, original})

	require.Equal(t, []*container.NodeTaint{
		{
			Key:    "dedicated",
			Value:  "tidb-shared",
			Effect: "NO_SCHEDULE",
		},
	}, got)
	require.NotSame(t, original, got[0])

	original.Value = "changed"
	require.Equal(t, "tidb-shared", got[0].Value)
}

func TestUpdateNodePoolNodeTaintsRequestPayload(t *testing.T) {
	tests := []struct {
		name       string
		taints     []*container.NodeTaint
		wantTaints []any
	}{
		{
			name: "sends non-empty taints",
			taints: []*container.NodeTaint{
				{
					Key:    "dedicated",
					Value:  "tidb-shared",
					Effect: "NO_SCHEDULE",
				},
			},
			wantTaints: []any{
				map[string]any{
					"key":    "dedicated",
					"value":  "tidb-shared",
					"effect": "NO_SCHEDULE",
				},
			},
		},
		{
			name:       "sends empty taints to clear existing taints",
			taints:     []*container.NodeTaint{},
			wantTaints: []any{},
		},
		{
			name: "filters nil entries before sending payload",
			taints: []*container.NodeTaint{
				nil,
				{
					Key:    "cloud.google.com/gke-nodepool",
					Value:  "pool-a",
					Effect: "NO_SCHEDULE",
				},
				nil,
			},
			wantTaints: []any{
				map[string]any{
					"key":    "cloud.google.com/gke-nodepool",
					"value":  "pool-a",
					"effect": "NO_SCHEDULE",
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var got map[string]any
			srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				require.Equal(t, http.MethodPut, r.Method)
				require.Equal(t, "/v1/projects/test-project/locations/us-central1/clusters/gke-test/nodePools/pool-a", r.URL.Path)
				require.NoError(t, json.NewDecoder(r.Body).Decode(&got))
				writeJSON(t, w, &container.Operation{Name: "op-1"})
			}))
			defer srv.Close()

			cli := newTestGCPClientSet(t, srv)
			op, err := cli.UpdateNodePoolNodeTaints(context.Background(), "projects/test-project/locations/us-central1/clusters/gke-test/nodePools/pool-a", tt.taints)
			require.NoError(t, err)
			require.Equal(t, "op-1", op.Name)

			require.Contains(t, got, "taints")
			taintsPayload, ok := got["taints"].(map[string]any)
			require.True(t, ok)
			require.Contains(t, taintsPayload, "taints")
			require.Equal(t, tt.wantTaints, taintsPayload["taints"])
		})
	}
}

func TestUpdateNodePoolNodeLabelsAndTaintsRequestPayload(t *testing.T) {
	var got map[string]any
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, http.MethodPut, r.Method)
		require.Equal(t, "/v1/projects/test-project/locations/us-central1/clusters/gke-test/nodePools/pool-a", r.URL.Path)
		require.NoError(t, json.NewDecoder(r.Body).Decode(&got))
		writeJSON(t, w, &container.Operation{Name: "op-1"})
	}))
	defer srv.Close()

	cli := newTestGCPClientSet(t, srv)
	op, err := cli.UpdateNodePoolNodeLabelsAndTaints(
		context.Background(),
		"projects/test-project/locations/us-central1/clusters/gke-test/nodePools/pool-a",
		map[string]string{
			"existing":                       "keep",
			"cloud.google.com/compute-class": "tikv-priority",
		},
		[]*container.NodeTaint{
			nil,
			{
				Key:    "cloud.google.com/compute-class",
				Value:  "tikv-priority",
				Effect: "NO_SCHEDULE",
			},
		},
	)
	require.NoError(t, err)
	require.Equal(t, "op-1", op.Name)

	labelsPayload, ok := got["labels"].(map[string]any)
	require.True(t, ok)
	require.Equal(t, map[string]any{
		"existing":                       "keep",
		"cloud.google.com/compute-class": "tikv-priority",
	}, labelsPayload["labels"])

	taintsPayload, ok := got["taints"].(map[string]any)
	require.True(t, ok)
	require.Equal(t, []any{
		map[string]any{
			"key":    "cloud.google.com/compute-class",
			"value":  "tikv-priority",
			"effect": "NO_SCHEDULE",
		},
	}, taintsPayload["taints"])
}

func TestUpdateNodePoolNodeLabelsAndTaintsRequestPayloadSendsEmptyValues(t *testing.T) {
	var got map[string]any
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, http.MethodPut, r.Method)
		require.Equal(t, "/v1/projects/test-project/locations/us-central1/clusters/gke-test/nodePools/pool-a", r.URL.Path)
		require.NoError(t, json.NewDecoder(r.Body).Decode(&got))
		writeJSON(t, w, &container.Operation{Name: "op-1"})
	}))
	defer srv.Close()

	cli := newTestGCPClientSet(t, srv)
	op, err := cli.UpdateNodePoolNodeLabelsAndTaints(
		context.Background(),
		"projects/test-project/locations/us-central1/clusters/gke-test/nodePools/pool-a",
		nil,
		nil,
	)
	require.NoError(t, err)
	require.Equal(t, "op-1", op.Name)

	labelsPayload, ok := got["labels"].(map[string]any)
	require.True(t, ok)
	require.Equal(t, map[string]any{}, labelsPayload["labels"])

	taintsPayload, ok := got["taints"].(map[string]any)
	require.True(t, ok)
	require.Equal(t, []any{}, taintsPayload["taints"])
}

func newTestGCPClientSet(t *testing.T, srv *httptest.Server) *ClientSet {
	t.Helper()

	containerSvc, err := container.NewService(context.Background(),
		option.WithHTTPClient(srv.Client()),
		option.WithoutAuthentication(),
	)
	require.NoError(t, err)
	containerSvc.BasePath = srv.URL + "/"

	return &ClientSet{container: containerSvc}
}
