package kubernetes

import (
	"context"
	"log"
	"path/filepath"
	"reflect"
	"strings"
	"testing"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"

	"github.com/go-kratos/kratos/v2/config"
)

const (
	testKey   = "test_config.json"
	namespace = "default"
	name      = "test"
)

var (
	keyPath    = strings.Join([]string{namespace, name, testKey}, "/")
	objectMeta = metav1.ObjectMeta{
		Name:      name,
		Namespace: namespace,
		Labels: map[string]string{
			"app": "test",
		},
	}
)

func TestSource(t *testing.T) {
	home := homedir.HomeDir()
	s := NewSource(
		Namespace("default"),
		LabelSelector(""),
		KubeConfig(filepath.Join(home, ".kube", "config")),
	)
	kvs, err := s.Load()
	if err != nil {
		t.Error(err)
	}
	for _, v := range kvs {
		t.Log(v)
	}
}

func ExampleNewSource() {
	conf := config.New(
		config.WithSource(
			NewSource(
				Namespace("mesh"),
				LabelSelector("app=test"),
				KubeConfig(filepath.Join(homedir.HomeDir(), ".kube", "config")),
			),
		),
	)
	err := conf.Load()
	if err != nil {
		log.Panic(err)
	}
}

func TestConfig(t *testing.T) {
	restConfig, err := rest.InClusterConfig()
	home := homedir.HomeDir()

	options := []Option{
		Namespace(namespace),
		LabelSelector("app=test"),
	}

	if err != nil {
		kubeconfig := filepath.Join(home, ".kube", "config")
		restConfig, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
		if err != nil {
			t.Fatal(err)
		}
		options = append(options, KubeConfig(kubeconfig))
	}
	clientSet, err := kubernetes.NewForConfig(restConfig)
	if err != nil {
		t.Fatal(err)
	}

	clientSetConfigMaps := clientSet.CoreV1().ConfigMaps(namespace)

	source := NewSource(options...)
	if _, err = clientSetConfigMaps.Create(context.Background(), &v1.ConfigMap{
		ObjectMeta: objectMeta,
		Data: map[string]string{
			testKey: "test config",
		},
	}, metav1.CreateOptions{}); err != nil {
		t.Fatal(err)
	}

	defer func() {
		if err = clientSetConfigMaps.Delete(context.Background(), name, metav1.DeleteOptions{}); err != nil {
			t.Error(err)
		}
	}()
	kvs, err := source.Load()
	if err != nil {
		t.Fatal(err)
	}
	if len(kvs) != 1 || kvs[0].Key != keyPath || string(kvs[0].Value) != "test config" {
		t.Fatal("config error")
	}

	w, err := source.Watch()
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		_ = w.Stop()
	}()
	// create also produce an event, discard it
	if _, err = w.Next(); err != nil {
		t.Fatal(err)
	}

	if _, err = clientSetConfigMaps.Update(context.Background(), &v1.ConfigMap{
		ObjectMeta: objectMeta,
		Data: map[string]string{
			testKey: "new config",
		},
	}, metav1.UpdateOptions{}); err != nil {
		t.Error(err)
	}

	if kvs, err = w.Next(); err != nil {
		t.Fatal(err)
	}

	if len(kvs) != 1 || kvs[0].Key != keyPath || string(kvs[0].Value) != "new config" {
		t.Fatal("config error")
	}
}

func TestExtToFormat(t *testing.T) {
	restConfig, err := rest.InClusterConfig()
	home := homedir.HomeDir()

	options := []Option{
		Namespace(namespace),
		LabelSelector("app=test"),
	}

	if err != nil {
		kubeconfig := filepath.Join(home, ".kube", "config")
		restConfig, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
		if err != nil {
			t.Fatal(err)
		}
		options = append(options, KubeConfig(kubeconfig))
	}
	clientSet, err := kubernetes.NewForConfig(restConfig)
	if err != nil {
		t.Fatal(err)
	}

	clientSetConfigMaps := clientSet.CoreV1().ConfigMaps(namespace)

	tc := `{"a":1}`
	if _, err = clientSetConfigMaps.Create(context.Background(), &v1.ConfigMap{
		ObjectMeta: objectMeta,
		Data: map[string]string{
			testKey: tc,
		},
	}, metav1.CreateOptions{}); err != nil {
		t.Fatal(err)
	}

	defer func() {
		if err = clientSetConfigMaps.Delete(context.Background(), name, metav1.DeleteOptions{}); err != nil {
			t.Error(err)
		}
	}()

	source := NewSource(options...)
	if err != nil {
		t.Fatal(err)
	}

	kvs, err := source.Load()
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(len(kvs), 1) {
		t.Errorf("len(kvs) = %d", len(kvs))
	}
	if !reflect.DeepEqual(keyPath, kvs[0].Key) {
		t.Errorf("kvs[0].Key is %s", kvs[0].Key)
	}
	if !reflect.DeepEqual(tc, string(kvs[0].Value)) {
		t.Errorf("kvs[0].Value is %s", kvs[0].Value)
	}
	if !reflect.DeepEqual("json", kvs[0].Format) {
		t.Errorf("kvs[0].Format is %s", kvs[0].Format)
	}
}