// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package multicluster

import (
	"fmt"
	"path/filepath"
	"testing"

	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"

	"istio.io/istio/pkg/kube/krt"
	"istio.io/istio/pkg/test"
	"istio.io/istio/pkg/test/util/assert"
	"istio.io/istio/pkg/test/util/file"
)

func TestClusterIDFromKubeconfig(t *testing.T) {
	cases := []struct {
		name    string
		cfg     *clientcmdapi.Config
		want    string
		wantErr bool
	}{
		{
			name: "current context",
			cfg: &clientcmdapi.Config{
				CurrentContext: "ctx",
				Contexts: map[string]*clientcmdapi.Context{
					"ctx": {Cluster: "cluster-a"},
				},
			},
			want: "cluster-a",
		},
		{
			name: "single context",
			cfg: &clientcmdapi.Config{
				Contexts: map[string]*clientcmdapi.Context{
					"ctx": {Cluster: "cluster-b"},
				},
			},
			want: "cluster-b",
		},
		{
			name: "multiple contexts are deterministic",
			cfg: &clientcmdapi.Config{
				Contexts: map[string]*clientcmdapi.Context{
					"z-context": {Cluster: "cluster-z"},
					"a-context": {Cluster: "cluster-a"},
				},
			},
			want: "cluster-a",
		},
		{
			name: "single cluster",
			cfg: &clientcmdapi.Config{
				Clusters: map[string]*clientcmdapi.Cluster{
					"cluster-c": {Server: "https://cluster-c.example.com"},
				},
			},
			want: "cluster-c",
		},
		{
			name: "multiple clusters are deterministic",
			cfg: &clientcmdapi.Config{
				Clusters: map[string]*clientcmdapi.Cluster{
					"cluster-z": {Server: "https://cluster-z.example.com"},
					"cluster-a": {Server: "https://cluster-a.example.com"},
				},
			},
			want: "cluster-a",
		},
		{
			name:    "no clusters",
			cfg:     &clientcmdapi.Config{},
			wantErr: true,
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got, err := clusterIDFromKubeconfig(tc.cfg)
			if tc.wantErr {
				assert.Error(t, err)
				return
			}
			assert.NoError(t, err)
			assert.Equal(t, got, tc.want)
		})
	}
}

func TestParseKubeconfig(t *testing.T) {
	cases := []struct {
		name       string
		data       []byte
		wantErr    bool
		wantLength int
		wantID     string
	}{
		{
			name:       "valid kubeconfig",
			data:       kubeconfigFileYAML("cluster-1"),
			wantLength: 1,
			wantID:     "cluster-1",
		},
		{
			name:    "malformed yaml",
			data:    []byte("::not yaml::"),
			wantErr: true,
		},
		{
			name:    "missing cluster",
			data:    []byte("apiVersion: v1\nkind: Config\n"),
			wantErr: true,
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			files, err := parseKubeconfig(tc.data)
			if tc.wantErr {
				assert.Error(t, err)
				return
			}
			assert.NoError(t, err)
			assert.Equal(t, len(files), tc.wantLength)
			assert.Equal(t, files[0].ClusterID, tc.wantID)
			assert.Equal(t, files[0].KubeconfigHash, kubeconfigHash(tc.data))
			assert.Equal(t, files[0].Kubeconfig, tc.data)
		})
	}
}

func TestNewKubeconfigCollection(t *testing.T) {
	stop := test.NewStop(t)
	root := t.TempDir()

	collection, err := NewKubeconfigCollection(root, krt.WithStop(stop))
	assert.NoError(t, err)
	assert.Equal(t, collection.WaitUntilSynced(stop), true)
	assert.Equal(t, collection.HasSynced(), true)

	tracker := assert.NewTracker[string](t)
	collection.Register(func(ev krt.Event[KubeconfigFile]) {
		tracker.Record(fmt.Sprintf("%s/%s", ev.Event.String(), ev.Latest().ResourceName()))
	})

	kubeconfig := kubeconfigFileYAML("cluster-1")
	file.WriteOrFail(t, filepath.Join(root, "remote.yaml"), kubeconfig)
	tracker.WaitOrdered(fmt.Sprintf("add/%s", kubeconfigHash(kubeconfig)))
}

func kubeconfigFileYAML(clusterID string) []byte {
	return kubeconfigFileYAMLWithToken(clusterID, "token")
}

func kubeconfigFileYAMLWithToken(clusterID, token string) []byte {
	return []byte(fmt.Sprintf(`apiVersion: v1
kind: Config
clusters:
- name: %s
  cluster:
    server: https://%s.example.com
contexts:
- name: %s-context
  context:
    cluster: %s
    user: %s-user
current-context: %s-context
users:
- name: %s-user
  user:
    token: %s
`, clusterID, clusterID, clusterID, clusterID, clusterID, clusterID, clusterID, token))
}
