package main

import (
	"bytes"
	"context"
	"encoding/base64"
	"encoding/json"
	"github.com/gin-gonic/gin"
	"github.com/onsi/gomega"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/operation-api/api"
	"github.com/tidbcloud/operation-api/crd"
	cloudv1 "github.com/tidbcloud/operationlet/api/v1"
	"io/ioutil"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"net/http"
	"net/http/httptest"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"testing"
	"time"
)

func newTestSvc(cli client.Client) *OperationSvc {
	e := gin.Default()
	infra := api.NewInfraClient(cli)
	return NewOperationSvc(infra, cli, e)
}

func TestOperationAPI(t *testing.T) {
	testcase := struct {
		name string
		crds []client.Object
	}{
		name: "test",
		crds: []client.Object{
			&corev1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Name:              "test-pod",
					Namespace:         "tidb1",
					CreationTimestamp: metav1.NewTime(time.Unix(1, 0)),
				},
				Status: corev1.PodStatus{
					Phase: corev1.PodRunning,
				},
			},
		},
	}

	g := gomega.NewGomegaWithT(t)
	scheme := runtime.NewScheme()
	g.Expect(corev1.AddToScheme(scheme)).To(gomega.Succeed())
	g.Expect(cloudv1.AddToScheme(scheme)).To(gomega.Succeed())
	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(testcase.crds...).Build()
	svc := newTestSvc(cli)
	svc.OperationsAPI()
	param := map[string]interface{}{
		"op_name": "testcase",
		"type":    RestartInstance,
		"args": crd.RestartInstance{
			TenantID:  1,
			ClusterID: 1,
			Instance:  "test-pod",
			OpName:    "testcase",
			Reason:    "unit test",
		},
	}
	resp, status := PostJson("/v1/operations/job", param, svc.Engine, "{\"exp\":11111111,\"user\":{\"email\":\"123@xx.com\",\"username\":\"123@xx.com\",\"csrfToken\":\"xxxxxxx\"}}")
	require.NotNil(t, resp)
	require.Equal(t, "200 OK", status)

	var crd cloudv1.OperationCommandJob
	namespacedName := types.NamespacedName{
		Name:      "testcase",
		Namespace: "tidb1",
	}
	err := cli.Get(context.TODO(), namespacedName, &crd)
	require.Nil(t, err)
	require.NotNil(t, crd)
	require.Equal(t, cloudv1.JobPhaseWaiting, crd.Status.Phase)
}

func TestApproveAPI(t *testing.T) {
	testcase := struct {
		name string
		crds []client.Object
	}{
		name: "test",
		crds: []client.Object{
			&cloudv1.OperationAuthorizeService{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "op_approvers",
					Namespace: "tidb_admin",
				},
				Spec: cloudv1.OperationAuthorizeServiceSpec{
					ApproverList: []string{"tester@email.com"},
				},
			},
			&cloudv1.OperationCommandJob{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "testcase",
					Namespace: "tidb2",
				},
				Spec: cloudv1.OperationCommandJobSpec{
					ClusterID: "2",
					TenantID:  "1",
					JobType:   cloudv1.RestartPod,
				},
				Status: cloudv1.OperationCommandJobStatus{
					Phase: cloudv1.JobPhaseWaiting,
				},
			},
		},
	}

	g := gomega.NewGomegaWithT(t)
	scheme := runtime.NewScheme()
	g.Expect(corev1.AddToScheme(scheme)).To(gomega.Succeed())
	g.Expect(cloudv1.AddToScheme(scheme)).To(gomega.Succeed())
	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(testcase.crds...).Build()
	svc := newTestSvc(cli)
	svc.ApproveAPI()
	param := map[string]interface{}{
		"op_name":    "testcase",
		"cluster_id": "2",
		"tenant_id":  "1",
	}
	_, status := PostJson("/v1/approve", param, svc.Engine, "{\"exp\":11111111,\"user\":{\"email\":\"123@xx.com\",\"username\":\"123@xx.com\",\"csrfToken\":\"xxxxxxx\"}}")
	require.Equal(t, "403 Forbidden", status)
	param = map[string]interface{}{
		"op_name":    "testcase",
		"cluster_id": "2",
		"tenant_id":  "1",
	}
	_, status = PostJson("/v1/approve", param, svc.Engine, "{\"exp\":11111111,\"user\":{\"email\":\"tester@email.com\",\"username\":\"123@xx.com\",\"csrfToken\":\"xxxxxxx\"}}")
	require.Equal(t, "200 OK", status)
	var crd cloudv1.OperationCommandJob
	namespacedName := types.NamespacedName{
		Name:      "testcase",
		Namespace: "tidb2",
	}
	err := cli.Get(context.TODO(), namespacedName, &crd)
	require.Nil(t, err)
	require.NotNil(t, crd)
	require.Equal(t, cloudv1.JobPhasePending, crd.Status.Phase)
}

func TestListAPI(t *testing.T) {
	testcase := struct {
		name string
		crds []client.Object
	}{
		name: "test",
		crds: []client.Object{
			&cloudv1.OperationCommandJob{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "testcase",
					Namespace: "tidb2",
				},
				Spec: cloudv1.OperationCommandJobSpec{
					ClusterID: "2",
					TenantID:  "1",
					Instance:  "db-tidb-1",
					JobType:   cloudv1.RestartPod,
				},
				Status: cloudv1.OperationCommandJobStatus{
					Phase: cloudv1.JobPhasePending,
				},
			},
			&cloudv1.OperationCommandJob{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "testcase2",
					Namespace: "tidb2",
				},
				Spec: cloudv1.OperationCommandJobSpec{
					ClusterID: "2",
					TenantID:  "1",
					Instance:  "db-tidb-2",
					JobType:   cloudv1.RestartPod,
				},
				Status: cloudv1.OperationCommandJobStatus{
					Phase: cloudv1.JobPhaseWaiting,
				},
			},
		},
	}

	g := gomega.NewGomegaWithT(t)
	scheme := runtime.NewScheme()
	g.Expect(corev1.AddToScheme(scheme)).To(gomega.Succeed())
	g.Expect(cloudv1.AddToScheme(scheme)).To(gomega.Succeed())
	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(testcase.crds...).Build()
	svc := newTestSvc(cli)
	svc.ListAPI()
	body := Get("/v1/operations/list?cluster_id=2&tenant_id=1", svc.Engine)
	require.NotNil(t, body)
	var resp map[string]interface{}
	require.Nil(t, json.Unmarshal(body, &resp))
	require.Len(t, resp["op_list"], 2)
}

func TestDetailAPI(t *testing.T) {

	// TODO
}

func PostJson(uri string, param map[string]interface{}, router *gin.Engine, cookie string) ([]byte, string) {
	header := []byte(cookie)
	enc := base64.StdEncoding.EncodeToString(header)
	jsonByte, _ := json.Marshal(param)
	req := httptest.NewRequest("POST", uri, bytes.NewReader(jsonByte))
	req.AddCookie(&http.Cookie{Name: "clinic.session_id", Value: enc})
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)
	result := w.Result()
	defer result.Body.Close()
	body, _ := ioutil.ReadAll(result.Body)
	return body, result.Status
}

func Get(uri string, router *gin.Engine) []byte {
	req := httptest.NewRequest("GET", uri, nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)
	result := w.Result()
	defer result.Body.Close()
	body, _ := ioutil.ReadAll(result.Body)
	return body
}
