package controllers

import (
	"fmt"
	"testing"
	"time"

	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
	"github.com/gardener/gardener/pkg/client/core/clientset/versioned/fake"
	"github.com/pingcap/dbaas/central/runtime/deployer/gardener"
	infraprovider "github.com/pingcap/dbaas/pkg/infra/provider"
	logutil "github.com/pingcap/dbaas/pkg/log"
	"github.com/stretchr/testify/assert"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	kubefake "k8s.io/client-go/kubernetes/fake"
	"k8s.io/utils/pointer"
)

const (
	testNamespace = "test"
	testName      = "test"
	certKey       = "tls.cert"
	basicCert     = "i am cert"
	newCert       = "i am new cert"
	oldCert       = "i am old cert"
)

var shootKubeConfig = `
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://fake.com
  name: shoot--nightly--test
contexts:
- context:
    cluster: shoot--nightly--test
    user: shoot--nightly--test-token
  name: shoot--nightly--test
current-context: shoot--nightly--test
kind: Config
preferences: {}
users:
- name: shoot--nightly--test-token
  user:
    token: REDACTED
`

func fakeShoot() *gardencorev1beta1.Shoot {
	return &gardencorev1beta1.Shoot{
		ObjectMeta: metav1.ObjectMeta{
			Name:      testName,
			Namespace: testNamespace,
			Labels: map[string]string{
				infraprovider.TenantTag: "10",
			},
		},
	}
}

func fakeKubeConfigSecret() *v1.Secret {
	return &v1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      fmt.Sprintf("%s.kubeconfig", testName),
			Namespace: testNamespace,
		},
		Data: map[string][]byte{
			"kubeconfig": []byte(shootKubeConfig),
		},
	}

}

func fakeCertSecret(content string) *v1.Secret {
	return &v1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      ClientAuthSecret,
			Namespace: "kube-system",
		},
		Data: map[string][]byte{
			certKey: []byte(content),
		},
	}
}
func TestShootLoggingController(t *testing.T) {
	log := logutil.Logger()

	fakeShoot := fakeShoot()
	fakeShootKubeConfigSecret := fakeKubeConfigSecret()
	fakeClientAuthSecret := fakeCertSecret(basicCert)

	fakeBaseClientSet := kubefake.NewSimpleClientset(fakeClientAuthSecret)
	fakeGardenerClientSet := kubefake.NewSimpleClientset(fakeShootKubeConfigSecret)
	fakeShootClientSet := kubefake.NewSimpleClientset()

	slc := &ShootLoggingController{
		Clientset:           fakeBaseClientSet,
		Log:                 log,
		LokiIngressHostName: "my-loki-ingress.com",
		LokiStackChartPath:  pointer.StringPtr("../../deploy/monitoring/manifest/loki-stack"),
		GardenerClient:      fakeGardenerClientSet,
		GardenerProjectName: pointer.StringPtr("test"),
		fakeShootClientSet:  fakeShootClientSet,
		dryRun:              true,
	}

	clientset := fake.NewSimpleClientset()
	stop := make(chan struct{})
	executedAddFunc := false
	gardener.StartGardenerController(log, testNamespace, clientset, slc.Delete,
		func(namespace string, name string, tenantID int64, cloud string, region string) error {
			assert.Equal(t, name, testName)
			assert.Equal(t, namespace, testNamespace)
			err := slc.Update(namespace, name, tenantID, cloud, region)
			assert.Nil(t, err, "Shoot logging controller update should be successful")
			executedAddFunc = true
			expectedLoggingNamespaceWillCreatedInShoot(fakeShootClientSet, t)
			expectedClientCertSecretWillCreatedInShoot(fakeShootClientSet, t)
			close(stop)
			return nil
		}, stop)

	_, err := clientset.CoreV1beta1().Shoots(testNamespace).Create(fakeShoot)
	assert.Nil(t, err)

	timeout := make(chan struct{})
	defer close(timeout)

	go func() {
		time.Sleep(10 * time.Second)
		timeout <- struct{}{}
	}()

	select {
	case <-stop:
		assert.Equal(t, executedAddFunc, true)
	case <-timeout:
		t.Fatal("timeout")
	}
}

func TestClientCertAutoRotate(t *testing.T) {
	log := logutil.Logger()
	testNamespace := "test"
	testName := "test"

	fakeShoot := fakeShoot()
	fakeShootKubeConfigSecret := fakeKubeConfigSecret()
	fakeNewClientAuthSecret := fakeCertSecret(newCert)
	fakeOldClientAuthSecret := fakeCertSecret(oldCert)

	fakeBaseClientSet := kubefake.NewSimpleClientset(fakeNewClientAuthSecret)
	fakeGardenerClientSet := kubefake.NewSimpleClientset(fakeShootKubeConfigSecret)
	fakeShootClientSet := kubefake.NewSimpleClientset(fakeOldClientAuthSecret)

	slc := &ShootLoggingController{
		Clientset:           fakeBaseClientSet,
		Log:                 log,
		LokiIngressHostName: "my-loki-ingress.com",
		LokiStackChartPath:  pointer.StringPtr("../../deploy/monitoring/manifest/loki-stack"),
		GardenerClient:      fakeGardenerClientSet,
		GardenerProjectName: pointer.StringPtr("test"),
		fakeShootClientSet:  fakeShootClientSet,
		dryRun:              true,
	}

	clientset := fake.NewSimpleClientset()
	stop := make(chan struct{})
	executedAddFunc := false
	gardener.StartGardenerController(log, testNamespace, clientset, slc.Delete,
		func(namespace string, name string, tenantID int64, cloud string, region string) error {
			assert.Equal(t, name, testName)
			assert.Equal(t, namespace, testNamespace)
			err := slc.Update(namespace, name, tenantID, cloud, region)
			assert.Nil(t, err, "Shoot logging controller update should be successful")
			executedAddFunc = true
			expectedLoggingNamespaceWillCreatedInShoot(fakeShootClientSet, t)
			expectedClientCertSecretWillCreatedInShoot(fakeShootClientSet, t)
			close(stop)
			return nil
		}, stop)

	_, err := clientset.CoreV1beta1().Shoots(testNamespace).Create(fakeShoot)
	assert.Nil(t, err)

	timeout := make(chan struct{})
	defer close(timeout)

	go func() {
		time.Sleep(10 * time.Second)
		timeout <- struct{}{}
	}()

	select {
	case <-stop:
		assert.Equal(t, executedAddFunc, true)
	case <-timeout:
		t.Fatal("timeout")
	}
}

func expectedLoggingNamespaceWillCreatedInShoot(shootClientSet kubernetes.Interface, t *testing.T) {
	ns, err := shootClientSet.CoreV1().Namespaces().Get(FluentbitReleaseNamespace, metav1.GetOptions{})
	if err != nil || ns == nil {
		t.Fatalf("namespace %s should be created in shoot", FluentbitReleaseNamespace)
	}
}

func expectedClientCertSecretWillCreatedInShoot(shootClientSet kubernetes.Interface, t *testing.T) {
	secret, err := shootClientSet.CoreV1().Secrets(FluentbitReleaseNamespace).Get(ClientAuthSecret, metav1.GetOptions{})
	if err != nil || secret == nil {
		t.Fatalf("secret %s/%s should be created in shoot, error: %v", FluentbitReleaseNamespace, ClientAuthSecret, err)
	}
}

func expectedClientCertRotated(shootClientSet kubernetes.Interface, t *testing.T) {
	secret, err := shootClientSet.CoreV1().Secrets(FluentbitReleaseNamespace).Get(ClientAuthSecret, metav1.GetOptions{})
	if err != nil || secret == nil {
		t.Fatalf("secret %s/%s should be created in shoot, error: %v", FluentbitReleaseNamespace, ClientAuthSecret, err)
	}
	if string(secret.Data[certKey]) != newCert {
		t.Fatalf("client cert should be auto rotate")
	}
}
