package gke

import (
	"context"
	"testing"
	"time"

	ec2v1alpha1 "github.com/crossplane-contrib/provider-aws/apis/ec2/v1alpha1"
	gkev1beta2 "github.com/crossplane-contrib/provider-gcp/apis/container/v1beta2"
	"github.com/go-logr/logr"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/mock/gomock"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/yaml"

	utiltime "github.com/tidbcloud/infra-provider/pkg/util/time"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/task"
	rmgrappbox "github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/appbox"
	rmgrvpc "github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/vpc"
	"github.com/tidbcloud/infra-provider/regional-server/test/testdata"
)

func TestIsSafeToApplyGKE(t *testing.T) {
	s := runtime.NewScheme()
	require.Nil(t, v1alpha1.SchemeBuilder.AddToScheme(s))
	require.Nil(t, ec2v1alpha1.SchemeBuilder.AddToScheme(s))
	require.Nil(t, gkev1beta2.SchemeBuilder.AddToScheme(s))

	config, err := config.LoadRootConfig("../../../../../examples/root-config-eks.yaml")
	require.Nil(t, err)
	defaultAPPBox := new(v1alpha1.APPBox)
	defaultVPC := new(v1alpha1.VPC)
	{
		bs, err := testdata.LoadGKEAPPBoxYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultAPPBox))
	}
	{
		bs, err := testdata.LoadGKEVPCYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultVPC))
	}
	{
		appbox := defaultAPPBox.DeepCopy()
		vpc := defaultVPC.DeepCopy()
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(vpc, appbox).WithStatusSubresource(vpc, appbox).Build()
		c, err := (&task.Context{
			Local:    kubeClient,
			Config:   config,
			VPC:      vpc,
			APPBox:   appbox,
			Logger:   logr.Discard(),
			APPBoxRW: rmgrappbox.NewAPPBoxReaderWriter(kubeClient),
			VPCR:     rmgrvpc.NewVPCReader(kubeClient),
		}).Default()
		require.Nil(t, err)
		// for now we do not have a way to mock aws.Client properly.
		gkeTask := NewGKE(c, nil)
		cluster, err := gkeTask.BuildGKE(context.Background())
		require.Nil(t, err)
		{
			ok, err := gkeTask.IsSafeToApplyGKE(context.Background(), cluster)
			require.Nil(t, err)
			require.True(t, ok)
		}
		{
			// IsSafeToApplyEKS does not check status, label and annotation
			cluster.Status.AtProvider.CurrentMasterVersion = "1.30.0-gke.1217000"
			if cluster.Annotations == nil {
				cluster.Annotations = map[string]string{}
			}
			if cluster.Labels == nil {
				cluster.Labels = map[string]string{}
			}
			cluster.Annotations["foo"] = "foo"
			cluster.Labels["foo"] = "foo"
			ok, err := gkeTask.IsSafeToApplyGKE(context.Background(), cluster)
			require.Nil(t, err)
			require.True(t, ok)
		}
		{
			// IsSafeToApplyEKS does not check status, label and annotation
			// Diff on InitialClusterVersion is also considered as safe
			cluster.Spec.ForProvider.InitialClusterVersion = ptr.To("1.30.0")
			ok, err := gkeTask.IsSafeToApplyGKE(context.Background(), cluster)
			require.Nil(t, err)
			require.True(t, ok)
		}
		{
			// Diff on WorkloadIdentityConfig is also considered as safe
			cluster.Spec.ForProvider.WorkloadIdentityConfig = nil
			ok, err := gkeTask.IsSafeToApplyGKE(context.Background(), cluster)
			require.Nil(t, err)
			require.True(t, ok)
		}
	}
}

func buildWantMaintenancePolicySpec(start, end string) *gkev1beta2.MaintenancePolicySpec {
	return &gkev1beta2.MaintenancePolicySpec{
		Window: gkev1beta2.MaintenanceWindowSpec{
			MaintenanceExclusions: map[string]gkev1beta2.TimeWindow{
				defaultMaintenanceExclusionName: {
					StartTime: start,
					EndTime:   end,
				},
			},
		},
	}
}

func TestGKE_buildMaintenancePolicy(t1 *testing.T) {
	tests := []struct {
		name      string
		config    config.GKEMaintenanceExclusionTimeWindow
		mockCalls func(clock *utiltime.MockClock)
		want      *gkev1beta2.MaintenancePolicySpec
	}{
		{
			name: "no maintenance window config",
		},
		{
			name: "enable auto update 1",
			config: config.GKEMaintenanceExclusionTimeWindow{
				AutoUpdate: true,
			},
			mockCalls: func(clock *utiltime.MockClock) {
				clock.EXPECT().Now().Return(time.Date(2025, 4, 2, 8, 0, 0, 0, time.UTC))
			},
			want: buildWantMaintenancePolicySpec("2025-04-01T00:00:00Z", "2025-05-01T00:00:00Z"),
		},
		{
			name: "enable auto update 2",
			config: config.GKEMaintenanceExclusionTimeWindow{
				AutoUpdate: true,
			},
			mockCalls: func(clock *utiltime.MockClock) {
				clock.EXPECT().Now().Return(time.Date(2025, 4, 2, 9, 5, 0, 0, time.UTC))
			},
			want: buildWantMaintenancePolicySpec("2025-04-01T00:00:00Z", "2025-05-01T00:00:00Z"),
		},
		{
			name: "enable auto update at month start",
			config: config.GKEMaintenanceExclusionTimeWindow{
				AutoUpdate: true,
			},
			mockCalls: func(clock *utiltime.MockClock) {
				clock.EXPECT().Now().Return(time.Date(2025, 4, 1, 0, 30, 0, 0, time.UTC))
			},
			want: buildWantMaintenancePolicySpec("2025-03-31T00:00:00Z", "2025-04-30T00:00:00Z"),
		},
		{
			name: "enable auto update in non-UTC timezone",
			config: config.GKEMaintenanceExclusionTimeWindow{
				AutoUpdate: true,
			},
			mockCalls: func(clock *utiltime.MockClock) {
				loc, _ := time.LoadLocation("America/New_York")
				now := time.Date(2025, 4, 2, 8, 0, 0, 0, loc)
				clock.EXPECT().Now().Return(now)
			},
			want: buildWantMaintenancePolicySpec("2025-04-01T00:00:00Z", "2025-05-01T00:00:00Z"),
		},
		{
			name: "disable auto update",
			config: config.GKEMaintenanceExclusionTimeWindow{
				StartTime: ptr.To(time.Date(2025, 4, 1, 0, 0, 0, 0, time.UTC)),
				EndTime:   ptr.To(time.Date(2025, 5, 1, 0, 0, 0, 0, time.UTC)),
			},
			want: buildWantMaintenancePolicySpec("2025-04-01T00:00:00Z", "2025-05-01T00:00:00Z"),
		},
	}
	for _, tt := range tests {
		t1.Run(tt.name, func(t1 *testing.T) {
			ctl := gomock.NewController(t1)
			mockClock := utiltime.NewMockClock(ctl)

			if tt.mockCalls != nil {
				tt.mockCalls(mockClock)
			}

			t := &GKE{
				BaseTask: &task.BaseTask{
					Context: &task.Context{
						Logger: logr.Discard(),
						Config: config.RootConfig{
							ControllerConfig: config.ControllerConfig{
								APPBoxConfig: config.APPBoxConfig{
									GKEMaintenanceExclusionTimeWindow: tt.config,
								},
							},
						},
					},
				},
				Clock: mockClock,
			}
			assert.Equal(t1, tt.want, t.buildMaintenancePolicy())
		})
	}
}
