package gc

import (
	"context"
	"errors"
	"testing"
	"time"

	logtesting "github.com/go-logr/logr/testing"
	. "github.com/onsi/gomega"
	prometheusv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
	observabilitypingcapcomv1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/resourceworker/controllers/fake"
	k8sapiv1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

func TestGcWorkerRun(t *testing.T) {
	g := NewGomegaWithT(t)
	cli := &fake.MockClient{
		MockList: fake.MockListFn(func(ctx context.Context, list runtime.Object, opts ...client.ListOption) error {
			return nil
		}),
	}
	ctx, cancel := context.WithCancel(context.Background())
	r := NewGC(1*time.Second, logtesting.NewTestLogger(t), cli)
	go func() {
		time.Sleep(3 * time.Second)
		cancel()
	}()
	r.Run(ctx)
	g.Expect(ctx.Err()).Should(Equal(context.Canceled))
}

func TestGCWorkerScan(t *testing.T) {
	type args struct {
		cli func(g Gomega) client.Client
	}

	type want struct {
		deletelist  interface{}
		pauselist   interface{}
		privatelist interface{}
		err         error
	}
	type caseType map[string]struct {
		args args
		want want
	}

	errBoom := errors.New("boom")
	cases := caseType{
		"ScanListFailed": {
			args: args{
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockList: fake.MockListFn(func(ctx context.Context, list runtime.Object, opts ...client.ListOption) error {
							return errBoom
						}),
					}
				},
			},
			want: want{
				err: errBoom,
			},
		},
		"ScanListPVCFailed": {
			args: args{
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockList: fake.MockListFn(func(ctx context.Context, list runtime.Object, opts ...client.ListOption) error {
							if _, ok := list.(*k8sapiv1.PersistentVolumeClaimList); ok {
								return errBoom
							}
							return nil
						}),
					}
				},
			},
			want: want{
				err: errBoom,
			},
		},
		"ScanListPVCEqueled": {
			args: args{
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockList: fake.MockListFn(func(ctx context.Context, list runtime.Object, opts ...client.ListOption) error {
							if l, ok := list.(*k8sapiv1.PersistentVolumeClaimList); ok {
								item := k8sapiv1.PersistentVolumeClaim{
									ObjectMeta: metav1.ObjectMeta{
										Name:   "test1",
										Labels: make(map[string]string),
									},
								}
								item.Labels["app.kubernetes.io/name"] = "prometheus"
								item.Labels["operator.prometheus.io/name"] = "test1"
								l.Items = append(l.Items, item)
							}
							if l, ok := list.(*prometheusv1.PrometheusList); ok {
								item := prometheusv1.Prometheus{
									ObjectMeta: metav1.ObjectMeta{
										Name:   "test1",
										Labels: make(map[string]string),
									},
								}
								l.Items = append(l.Items, &item)
							}
							return nil
						}),
					}
				},
			},
			want: want{
				err: nil,
			},
		},
		"ScanListPVCandClustersShouldDelete": {
			args: args{
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockList: fake.MockListFn(func(ctx context.Context, list runtime.Object, opts ...client.ListOption) error {
							if l, ok := list.(*k8sapiv1.PersistentVolumeClaimList); ok {
								item := k8sapiv1.PersistentVolumeClaim{
									ObjectMeta: metav1.ObjectMeta{
										Name:   "test1",
										Labels: make(map[string]string),
									},
								}
								item.Labels["app.kubernetes.io/name"] = "prometheus"
								item.Labels["operator.prometheus.io/name"] = "test1"
								l.Items = append(l.Items, item)
							}
							if l, ok := list.(*observabilitypingcapcomv1.ClusterList); ok {
								item := observabilitypingcapcomv1.Cluster{
									ObjectMeta: metav1.ObjectMeta{
										Name:   "test1",
										Labels: make(map[string]string),
									},
								}
								item.Labels[constants.LabelTimestampToDelete] = "1664198139"
								item.Status.Phase = constants.TO_BE_DELETED
								l.Items = append(l.Items, item)

								clusterToBePaused := observabilitypingcapcomv1.Cluster{
									ObjectMeta: metav1.ObjectMeta{
										Name:   "test2",
										Labels: make(map[string]string),
									},
								}
								clusterToBePaused.Labels[constants.LabelTimestampToPause] = "1664198139"
								clusterToBePaused.Status.Phase = constants.TO_BE_PAUSED
								l.Items = append(l.Items, clusterToBePaused)
							}
							return nil
						}),
					}
				},
			},
			want: want{
				deletelist: func() []client.Object {
					item := k8sapiv1.PersistentVolumeClaim{
						ObjectMeta: metav1.ObjectMeta{
							Name:   "test1",
							Labels: make(map[string]string),
						},
					}
					item.Labels["app.kubernetes.io/name"] = "prometheus"
					item.Labels["operator.prometheus.io/name"] = "test1"
					item2 := observabilitypingcapcomv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name:   "test1",
							Labels: make(map[string]string),
						},
					}
					item2.Labels[constants.LabelTimestampToDelete] = "1664198139"
					item2.Status.Phase = constants.TO_BE_DELETED
					var deletelist []client.Object
					deletelist = append(deletelist, &item, &item2)
					return deletelist
				}(),
				pauselist: []client.Object{
					&observabilitypingcapcomv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name: "test2",
							Labels: map[string]string{
								constants.LabelTimestampToPause: "1664198139",
							},
						},
						Status: observabilitypingcapcomv1.ClusterStatus{
							Phase: constants.TO_BE_PAUSED,
						},
					},
				},
				err: nil,
			},
		},
		"ScanPrivateLinkShouldDelete": {
			args: args{
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockList: fake.MockListFn(func(ctx context.Context, list runtime.Object, opts ...client.ListOption) error {
							if l, ok := list.(*observabilitypingcapcomv1.PrivateEndpointList); ok {
								item := observabilitypingcapcomv1.PrivateEndpoint{
									ObjectMeta: metav1.ObjectMeta{
										Name:   "test1",
										Labels: make(map[string]string),
									},
								}
								item.Labels[constants.LabelTenantID] = "123"
								item.Labels[constants.LabelBizType] = "test1"
								item.Labels[constants.LabelProjectID] = "321"
								l.Items = append(l.Items, item)
							}
							return nil
						}),
					}
				},
			},
			want: want{
				privatelist: []client.Object{
					&observabilitypingcapcomv1.PrivateEndpoint{
						ObjectMeta: metav1.ObjectMeta{
							Name: "test1",
							Labels: map[string]string{
								constants.LabelTenantID:  "123",
								constants.LabelBizType:   "test1",
								constants.LabelProjectID: "321",
							},
						},
					},
				},
				err: nil,
			},
		},
		"ScanPrivateLinkShouldNotDelete": {
			args: args{
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockList: fake.MockListFn(func(ctx context.Context, list runtime.Object, opts ...client.ListOption) error {
							if l, ok := list.(*observabilitypingcapcomv1.PrivateEndpointList); ok {
								item := observabilitypingcapcomv1.PrivateEndpoint{
									ObjectMeta: metav1.ObjectMeta{
										Name:   "test1",
										Labels: make(map[string]string),
									},
								}
								item.Labels[constants.LabelTenantID] = "123"
								item.Labels[constants.LabelBizType] = "test1"
								item.Labels[constants.LabelProjectID] = "321"
								l.Items = append(l.Items, item)
							}
							if l, ok := list.(*observabilitypingcapcomv1.ClusterList); ok {
								// project  equal
								item := observabilitypingcapcomv1.Cluster{
									ObjectMeta: metav1.ObjectMeta{
										Name:      "test2",
										Labels:    make(map[string]string),
										Namespace: "test1-123",
									},
								}
								item.Labels[constants.LabelTenantID] = "123"
								item.Labels[constants.LabelBizType] = "test1"
								item.Labels[constants.LabelProjectID] = "321"
								l.Items = append(l.Items, item)

							}
							return nil
						}),
					}
				},
			},
			want: want{
				privatelist: nil,
				err:         nil,
			},
		},
	}
	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			r := NewGC(1*time.Second, logtesting.NewTestLogger(t), tc.args.cli(g))
			deletelist, pauselist, privatelist, err := r.scan(context.Background())
			if tc.want.err == nil {
				g.Expect(err).Should(BeNil())
			} else {
				g.Expect(err).Should(Equal(tc.want.err))
			}

			if tc.want.deletelist == nil {
				g.Expect(deletelist).Should(BeNil())
			}
			if tc.want.pauselist == nil {
				g.Expect(pauselist).Should(BeNil())
			}
			if tc.want.privatelist == nil {
				g.Expect(privatelist).Should(BeNil())
			}
			if tc.want.deletelist != nil {
				g.Expect(deletelist).Should(Equal(tc.want.deletelist))
			}
			if tc.want.pauselist != nil {
				g.Expect(pauselist).Should(Equal(tc.want.pauselist))
			}
			if tc.want.privatelist != nil {
				g.Expect(privatelist).Should(Equal(tc.want.privatelist))
			}

		})
	}
}

func TestGcWorkerDelete(t *testing.T) {

	errBoom := errors.New("boom")
	t.Run("DeleteFailed", func(t *testing.T) {
		g := NewGomegaWithT(t)
		cli := &fake.MockClient{
			MockDelete: fake.MockDeleteFn(func(ctx context.Context, obj runtime.Object, opts ...client.DeleteOption) error {
				return errBoom
			}),
		}
		item := k8sapiv1.PersistentVolumeClaim{
			ObjectMeta: metav1.ObjectMeta{
				Name:   "test1",
				Labels: make(map[string]string),
			},
		}
		item.Labels["app.kubernetes.io/name"] = "prometheus"
		item.Labels["operator.prometheus.io/name"] = "test1"
		var deletelist []client.Object
		deletelist = append(deletelist, &item)
		r := NewGC(1*time.Second, logtesting.NewTestLogger(t), cli)
		g.Expect(r.delete(context.Background(), deletelist)).Should(HaveOccurred())

	})
	t.Run("DeleteSuccess", func(t *testing.T) {
		g := NewGomegaWithT(t)
		cli := &fake.MockClient{
			MockDelete: fake.MockDeleteFn(func(ctx context.Context, obj runtime.Object, opts ...client.DeleteOption) error {
				return nil
			}),
		}
		item := k8sapiv1.PersistentVolumeClaim{
			ObjectMeta: metav1.ObjectMeta{
				Name:   "test1",
				Labels: make(map[string]string),
			},
		}
		item.Labels["app.kubernetes.io/name"] = "prometheus"
		item.Labels["operator.prometheus.io/name"] = "test1"
		var deletelist []client.Object
		deletelist = append(deletelist, &item)
		r := NewGC(1*time.Second, logtesting.NewTestLogger(t), cli)
		g.Expect(r.delete(context.Background(), deletelist)).Should(Succeed())

	})
}

func TestGcPauseClusters(t *testing.T) {

	errBoom := errors.New("boom")
	t.Run("PauseClusterShared", func(t *testing.T) {
		g := NewGomegaWithT(t)
		cli := &fake.MockClient{
			MockDelete: fake.MockDeleteFn(func(ctx context.Context, obj runtime.Object, opts ...client.DeleteOption) error {
				return errBoom
			}),
		}
		item := observabilitypingcapcomv1.Cluster{
			ObjectMeta: metav1.ObjectMeta{
				Name:        "test1",
				Annotations: make(map[string]string),
			},
		}
		item.Annotations["observability.pingcap.com/share-mode"] = "shared"
		var list []client.Object
		list = append(list, &item)
		r := NewGC(1*time.Second, logtesting.NewTestLogger(t), cli)
		g.Expect(r.pauseClusters(context.Background(), list)).Should(Succeed())

	})
}
