package proxy

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"
	pb "github.com/tidbcloud/cluster-service-ng/api/tidbcloud/dataplane-manager/v1"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	bizerr "github.com/tidbcloud/infra-provider/regional-server/pkg/common/errors"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/repository"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/dynamic"
	dynamicfake "k8s.io/client-go/dynamic/fake"
	"k8s.io/client-go/rest"
	ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func TestDataPlaneK8sServer_ListResources(t *testing.T) {
	t.Setenv("KUBECONFIG", "")

	scheme := runtime.NewScheme()
	require.NoError(t, v1alpha1.AddToScheme(scheme))

	localClient := fake.NewClientBuilder().
		WithScheme(scheme).
		WithObjects(
			&v1alpha1.DataPlane{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "dp-1",
					Namespace: "tidbcloud",
				},
				Status: v1alpha1.DataPlaneStatus{
					AppBox: &v1alpha1.Reference{
						Name:      "appbox-1",
						Namespace: "tidbcloud",
					},
				},
			},
			&v1alpha1.APPBox{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "appbox-1",
					Namespace: "tidbcloud",
				},
			},
		).
		Build()

	deploymentGVR := schema.GroupVersionResource{
		Group:    "apps",
		Version:  "v1",
		Resource: "deployments",
	}
	deployment := &unstructured.Unstructured{
		Object: map[string]interface{}{
			"apiVersion": "apps/v1",
			"kind":       "Deployment",
			"metadata": map[string]interface{}{
				"name":      "demo",
				"namespace": "default",
			},
		},
	}

	restoreConfigFromAPPBox := configFromAPPBox
	restoreDynamicFactory := newDynamicClientForConfig
	t.Cleanup(func() {
		configFromAPPBox = restoreConfigFromAPPBox
		newDynamicClientForConfig = restoreDynamicFactory
	})

	configFromAPPBox = func(
		_ context.Context,
		_ ctrlclient.Client,
		_ *v1alpha1.APPBox,
		_ *runtime.Scheme,
		_ *v1alpha1.AccountReference,
	) (*rest.Config, error) {
		return &rest.Config{Host: "https://dataplane.example"}, nil
	}
	newDynamicClientForConfig = func(_ *rest.Config) (dynamic.Interface, error) {
		return dynamicfake.NewSimpleDynamicClientWithCustomListKinds(
			runtime.NewScheme(),
			map[schema.GroupVersionResource]string{
				deploymentGVR: "DeploymentList",
			},
			deployment,
		), nil
	}

	server := NewDataPlaneK8sServer(
		localClient,
		repository.NewDataPlaneReaderWriter(localClient),
		repository.NewAPPBoxReaderWriter(localClient),
		"tidbcloud",
	)

	resp, err := server.ListResources(context.Background(), &pb.DataPlaneListResourcesRequest{
		DataPlaneId:    "dp-1",
		Group:          "apps",
		Version:        "v1",
		Namespace:      "default",
		ResourcePlural: "deployments",
	})
	require.NoError(t, err)

	items, ok := resp.AsMap()["items"].([]interface{})
	require.True(t, ok)
	require.Len(t, items, 1)

	item, ok := items[0].(map[string]interface{})
	require.True(t, ok)
	metadata, ok := item["metadata"].(map[string]interface{})
	require.True(t, ok)
	require.Equal(t, "demo", metadata["name"])
}

func TestDataPlaneK8sServer_GetResource(t *testing.T) {
	t.Setenv("KUBECONFIG", "")

	scheme := runtime.NewScheme()
	require.NoError(t, v1alpha1.AddToScheme(scheme))

	localClient := fake.NewClientBuilder().
		WithScheme(scheme).
		WithObjects(
			&v1alpha1.DataPlane{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "dp-1",
					Namespace: "tidbcloud",
				},
				Status: v1alpha1.DataPlaneStatus{
					AppBox: &v1alpha1.Reference{
						Name:      "appbox-1",
						Namespace: "tidbcloud",
					},
				},
			},
			&v1alpha1.APPBox{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "appbox-1",
					Namespace: "tidbcloud",
				},
			},
		).
		Build()

	deployment := &unstructured.Unstructured{
		Object: map[string]interface{}{
			"apiVersion": "apps/v1",
			"kind":       "Deployment",
			"metadata": map[string]interface{}{
				"name":      "demo",
				"namespace": "default",
			},
		},
	}

	restoreConfigFromAPPBox := configFromAPPBox
	restoreDynamicFactory := newDynamicClientForConfig
	t.Cleanup(func() {
		configFromAPPBox = restoreConfigFromAPPBox
		newDynamicClientForConfig = restoreDynamicFactory
	})

	configFromAPPBox = func(
		_ context.Context,
		_ ctrlclient.Client,
		_ *v1alpha1.APPBox,
		_ *runtime.Scheme,
		_ *v1alpha1.AccountReference,
	) (*rest.Config, error) {
		return &rest.Config{Host: "https://dataplane.example"}, nil
	}
	newDynamicClientForConfig = func(_ *rest.Config) (dynamic.Interface, error) {
		return dynamicfake.NewSimpleDynamicClient(runtime.NewScheme(), deployment), nil
	}

	server := NewDataPlaneK8sServer(
		localClient,
		repository.NewDataPlaneReaderWriter(localClient),
		repository.NewAPPBoxReaderWriter(localClient),
		"tidbcloud",
	)

	resp, err := server.GetResource(context.Background(), &pb.DataPlaneGetResourceRequest{
		DataPlaneId:    "dp-1",
		Group:          "apps",
		Version:        "v1",
		Namespace:      "default",
		ResourcePlural: "deployments",
		Name:           "demo",
	})
	require.NoError(t, err)

	metadata, ok := resp.AsMap()["metadata"].(map[string]interface{})
	require.True(t, ok)
	require.Equal(t, "demo", metadata["name"])
}

func TestDataPlaneK8sServer_ListResourcesWithoutAppBoxRef(t *testing.T) {
	scheme := runtime.NewScheme()
	require.NoError(t, v1alpha1.AddToScheme(scheme))

	localClient := fake.NewClientBuilder().
		WithScheme(scheme).
		WithObjects(
			&v1alpha1.DataPlane{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "dp-1",
					Namespace: "tidbcloud",
				},
			},
		).
		Build()

	server := NewDataPlaneK8sServer(
		localClient,
		repository.NewDataPlaneReaderWriter(localClient),
		repository.NewAPPBoxReaderWriter(localClient),
		"tidbcloud",
	)

	_, err := server.ListResources(context.Background(), &pb.DataPlaneListResourcesRequest{
		DataPlaneId:    "dp-1",
		Version:        "v1",
		ResourcePlural: "pods",
	})
	require.Error(t, err)
	require.True(t, bizerr.Is(err, bizerr.ParameterInvalid))
}
