package internet

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/message"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/test/resourcemanager/mockinternet"
	"github.com/tidbcloud/infra-provider/regional-server/test/resourcemanager/mockvpc"
	"go.uber.org/mock/gomock"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

func TestCreateInternet(t *testing.T) {
	ctl := gomock.NewController(t)
	vpcr := mockvpc.NewMockVPCReader(ctl)
	internetrw := mockinternet.NewMockInternetReaderWriter(ctl)
	internetsvc := NewInternetService(internetrw, vpcr, config.RootConfig{
		ControllerConfig: config.ControllerConfig{
			Provider: consts.ProviderTypeEKS,
			Env:      "test",
			RegionConfig: config.RegionConfig{
				Name: "us-west-1",
			},
		},
	})
	t.Run("vpc not found", func(t *testing.T) {
		vpcr.EXPECT().List(context.TODO(), &v1alpha1.VPCList{}, client.MatchingLabels{
			v1alpha1.VPCIDKey: "vpc-123",
		}).Return(nil).Times(1)
		_, err := internetsvc.CreateInternet(context.TODO(), &message.CreateInternetReq{
			VPCID: "vpc-123",
		})
		assert.ErrorContains(t, err, "vpc not found")
	})
	t.Run("internet already exists", func(t *testing.T) {
		vpcr.EXPECT().List(context.TODO(), &v1alpha1.VPCList{}, client.MatchingLabels{
			v1alpha1.VPCIDKey: "vpc-123",
		}).DoAndReturn(
			func(ctx context.Context, list *v1alpha1.VPCList, opts ...client.ListOption) error {
				list.Items = []v1alpha1.VPC{{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "vpc-test",
						Namespace: "test",
						Labels: map[string]string{
							v1alpha1.VPCIDKey: "vpc-123",
						},
					}},
				}
				return nil
			}).Times(1)
		internetrw.EXPECT().List(context.TODO(), &v1alpha1.InternetList{}, client.MatchingLabels{
			v1alpha1.VPCNameKey: "vpc-test",
		}).DoAndReturn(
			func(ctx context.Context, list *v1alpha1.InternetList, opts ...client.ListOption) error {
				list.Items = []v1alpha1.Internet{{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "internet-test",
						Namespace: "test",
						Labels: map[string]string{
							v1alpha1.VPCNameKey: "vpc-test",
						},
					},
					Status: v1alpha1.InternetStatus{
						Phase: v1alpha1.InternetPhaseActive,
					},
				}}
				return nil
			}).Times(1)
		_, err := internetsvc.CreateInternet(context.TODO(), &message.CreateInternetReq{
			VPCID: "vpc-123",
		})
		assert.ErrorContains(t, err, "internet already exists")
	})
	t.Run("exist readyfordelete internet", func(t *testing.T) {
		vpcr.EXPECT().List(context.TODO(), &v1alpha1.VPCList{}, client.MatchingLabels{
			v1alpha1.VPCIDKey: "vpc-123",
		}).DoAndReturn(
			func(ctx context.Context, list *v1alpha1.VPCList, opts ...client.ListOption) error {
				list.Items = []v1alpha1.VPC{{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "vpc-test",
						Namespace: "test",
						Labels: map[string]string{
							v1alpha1.VPCIDKey: "vpc-123",
						},
					}},
				}
				return nil
			}).Times(1)
		internetrw.EXPECT().List(context.TODO(), &v1alpha1.InternetList{}, client.MatchingLabels{
			v1alpha1.VPCNameKey: "vpc-test",
		}).DoAndReturn(
			func(ctx context.Context, list *v1alpha1.InternetList, opts ...client.ListOption) error {
				list.Items = []v1alpha1.Internet{{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "internet-test",
						Namespace: "test",
						Labels: map[string]string{
							v1alpha1.VPCNameKey: "vpc-test",
						},
					},
					Status: v1alpha1.InternetStatus{
						Phase: v1alpha1.InternetPhaseReadyForDelete,
					},
				}}
				return nil
			}).Times(1)
		internetrw.EXPECT().UpdateStatus(context.TODO(), &v1alpha1.Internet{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "internet-test",
				Namespace: "test",
				Labels: map[string]string{
					v1alpha1.VPCNameKey: "vpc-test",
				},
			},
			Status: v1alpha1.InternetStatus{
				Phase: v1alpha1.InternetPhaseActive,
			},
		}).Return(nil).Times(1)
		_, err := internetsvc.CreateInternet(context.TODO(), &message.CreateInternetReq{
			VPCID: "vpc-123",
		})
		assert.ErrorContains(t, err, "internet already exists")
	})
	t.Run("create internet failed", func(t *testing.T) {
		vpcr.EXPECT().List(context.TODO(), &v1alpha1.VPCList{}, client.MatchingLabels{
			v1alpha1.VPCIDKey: "vpc-123",
		}).DoAndReturn(
			func(ctx context.Context, list *v1alpha1.VPCList, opts ...client.ListOption) error {
				list.Items = []v1alpha1.VPC{{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "vpc-test",
						Namespace: "test",
						Labels: map[string]string{
							v1alpha1.VPCIDKey: "vpc-123",
						},
					}},
				}
				return nil
			}).Times(1)
		internetrw.EXPECT().List(context.TODO(), &v1alpha1.InternetList{}, client.MatchingLabels{
			v1alpha1.VPCNameKey: "vpc-test",
		}).Return(nil).Times(1)
		internetrw.EXPECT().Create(context.TODO(), &v1alpha1.Internet{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "vpc-test-0",
				Namespace: "test",
				Labels: map[string]string{
					v1alpha1.VPCNameKey:    "vpc-test",
					v1alpha1.RegionKey:     internetsvc.config.RegionConfig.Name,
					v1alpha1.TagProjectKey: "",
					v1alpha1.TagTenantKey:  "",
				},
			},
			Spec: v1alpha1.InternetSpec{
				Provider: internetsvc.config.Provider,
				Region:   internetsvc.config.RegionConfig.Name,
				VPCRef: &v1alpha1.Reference{
					Name:      "vpc-test",
					Namespace: "test",
				},
			},
		}).Return(assert.AnError).Times(1)
		_, err := internetsvc.CreateInternet(context.TODO(), &message.CreateInternetReq{
			VPCID: "vpc-123",
		})
		assert.ErrorContains(t, err, "failed to create internet")
	})
	t.Run("create internet success", func(t *testing.T) {
		vpcr.EXPECT().List(context.TODO(), &v1alpha1.VPCList{}, client.MatchingLabels{
			v1alpha1.VPCIDKey: "vpc-123",
		}).DoAndReturn(
			func(ctx context.Context, list *v1alpha1.VPCList, opts ...client.ListOption) error {
				list.Items = []v1alpha1.VPC{{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "vpc-test",
						Namespace: "test",
						Labels: map[string]string{
							v1alpha1.VPCIDKey: "vpc-123",
						},
					}},
				}
				return nil
			}).Times(1)
		internetrw.EXPECT().List(context.TODO(), &v1alpha1.InternetList{}, client.MatchingLabels{
			v1alpha1.VPCNameKey: "vpc-test",
		}).Return(nil).Times(1)
		targetInternet := &v1alpha1.Internet{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "vpc-test-0",
				Namespace: "test",
				Labels: map[string]string{
					v1alpha1.VPCNameKey:    "vpc-test",
					v1alpha1.RegionKey:     internetsvc.config.RegionConfig.Name,
					v1alpha1.TagProjectKey: "",
					v1alpha1.TagTenantKey:  "",
				},
			},
			Spec: v1alpha1.InternetSpec{
				Provider: internetsvc.config.Provider,
				Region:   internetsvc.config.RegionConfig.Name,
				VPCRef: &v1alpha1.Reference{
					Name:      "vpc-test",
					Namespace: "test",
				},
			},
		}
		internetrw.EXPECT().Create(context.TODO(), targetInternet).Return(nil).Times(1)
		internet, err := internetsvc.CreateInternet(context.TODO(), &message.CreateInternetReq{
			VPCID: "vpc-123",
		})
		assert.Nil(t, err)
		assert.Equal(t, targetInternet, internet)
	})
}

func TestDeleteInternet(t *testing.T) {
	ctl := gomock.NewController(t)
	internetrw := mockinternet.NewMockInternetReaderWriter(ctl)
	internetsvc := NewInternetService(internetrw, nil, config.RootConfig{
		ControllerConfig: config.ControllerConfig{
			Provider: consts.ProviderTypeEKS,
			Env:      "test",
			RegionConfig: config.RegionConfig{
				Name: "us-west-1",
			},
		},
	})
	t.Run("internet not found", func(t *testing.T) {
		internetrw.EXPECT().Delete(context.TODO(), &v1alpha1.Internet{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "internet-test",
				Namespace: "test",
			},
		}).Return(apierrors.NewNotFound(schema.GroupResource{}, "")).Times(1)
		err := internetsvc.DeleteInternet(context.TODO(), &message.DeleteInternetReq{
			Name:      "internet-test",
			Namespace: "test",
		})
		assert.ErrorContains(t, err, "internet not found")
	})
	t.Run("delete internet failed", func(t *testing.T) {
		internetrw.EXPECT().Delete(context.TODO(), &v1alpha1.Internet{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "internet-test",
				Namespace: "test",
			},
		}).Return(assert.AnError).Times(1)
		err := internetsvc.DeleteInternet(context.TODO(), &message.DeleteInternetReq{
			Name:      "internet-test",
			Namespace: "test",
		})
		assert.ErrorContains(t, err, "failed to delete internet")
	})
	t.Run("delete internet success", func(t *testing.T) {
		internetrw.EXPECT().Delete(context.TODO(), &v1alpha1.Internet{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "internet-test",
				Namespace: "test",
			},
		}).Return(nil).Times(1)
		err := internetsvc.DeleteInternet(context.TODO(), &message.DeleteInternetReq{
			Name:      "internet-test",
			Namespace: "test",
		})
		assert.Nil(t, err)
	})
}

func TestGetInternet(t *testing.T) {
	ctl := gomock.NewController(t)
	internetrw := mockinternet.NewMockInternetReaderWriter(ctl)
	internetsvc := NewInternetService(internetrw, nil, config.RootConfig{
		ControllerConfig: config.ControllerConfig{
			Provider: consts.ProviderTypeEKS,
			Env:      "test",
			RegionConfig: config.RegionConfig{
				Name: "us-west-1",
			},
		},
	})
	t.Run("internet not found", func(t *testing.T) {
		internetrw.EXPECT().Get(context.TODO(), client.ObjectKey{
			Name:      "internet-test",
			Namespace: "test",
		}, &v1alpha1.Internet{}).Return(apierrors.NewNotFound(schema.GroupResource{}, "")).Times(1)
		_, err := internetsvc.GetInternet(context.TODO(), &message.GetInternetReq{
			Name:      "internet-test",
			Namespace: "test",
		})
		assert.ErrorContains(t, err, "internet not found")
	})
	t.Run("get internet failed", func(t *testing.T) {
		internetrw.EXPECT().Get(context.TODO(), client.ObjectKey{
			Name:      "internet-test",
			Namespace: "test",
		}, &v1alpha1.Internet{}).Return(assert.AnError).Times(1)
		_, err := internetsvc.GetInternet(context.TODO(), &message.GetInternetReq{
			Name:      "internet-test",
			Namespace: "test",
		})
		assert.ErrorContains(t, err, "failed to get internet")
	})
	t.Run("get internet success", func(t *testing.T) {
		targetInternet := &v1alpha1.Internet{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "internet-test",
				Namespace: "test",
			},
		}
		internetrw.EXPECT().Get(context.TODO(), client.ObjectKey{
			Name:      "internet-test",
			Namespace: "test",
		}, &v1alpha1.Internet{}).DoAndReturn(
			func(ctx context.Context, key client.ObjectKey, obj *v1alpha1.Internet) error {
				*obj = *targetInternet
				return nil
			}).Times(1)
		internet, err := internetsvc.GetInternet(context.TODO(), &message.GetInternetReq{
			Name:      "internet-test",
			Namespace: "test",
		})
		assert.Nil(t, err)
		assert.Equal(t, targetInternet, internet)
	})
}
