package clustereventconsumer

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/eventbus-datastruct/event"
	"go.uber.org/mock/gomock"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/nextgenglobal/mock_nextgenglobal"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	eventutil "github.com/tidbcloud/tidb-management-service/utilities/clusterevent"
)

func TestIsNextGenInstancePlan(t *testing.T) {
	require.True(t, isNextGenInstancePlan("Premium"))
	require.True(t, isNextGenInstancePlan("BYOC"))
	require.True(t, isNextGenInstancePlan("Premium_Reserved"))
	require.True(t, isNextGenInstancePlan("Essential_V2"))
	require.False(t, isNextGenInstancePlan("Starter"))
}

func TestRestoreEventsAreMappedToDisplayEvents(t *testing.T) {
	require.Equal(t, "Start to restore cluster.", clusterEventSummaryMap[eventutil.EventTypeRestoring])
	require.Equal(t, "Cluster successfully restored.", clusterEventSummaryMap[eventutil.EventTypeRestored])
	require.Equal(t, "RestoreCluster", clusterEventTypeMap[eventutil.EventTypeRestoring])
	require.Equal(t, "RestoreCluster", clusterEventTypeMap[eventutil.EventTypeRestored])
	require.Equal(t, event.EventStatusStarted, clusterEventStatusMap[eventutil.EventTypeRestoring])
	require.Equal(t, event.EventStatusSucceeded, clusterEventStatusMap[eventutil.EventTypeRestored])

	require.Equal(t, "Start to restore instance.", instanceEventSummaryMap[eventutil.EventTypeRestoring])
	require.Equal(t, "Instance successfully restored.", instanceEventSummaryMap[eventutil.EventTypeRestored])
	require.Equal(t, "RestoreInstance", instanceEventTypeMap[eventutil.EventTypeRestoring])
	require.Equal(t, "RestoreInstance", instanceEventTypeMap[eventutil.EventTypeRestored])
	require.Equal(t, event.EventStatusStarted, instanceEventStatusMap[eventutil.EventTypeRestoring])
	require.Equal(t, event.EventStatusSucceeded, instanceEventStatusMap[eventutil.EventTypeRestored])
}

func TestInstanceMutationEventsAreMappedToDisplayEvents(t *testing.T) {
	require.Equal(t, event.EventStatusStarted, clusterEventStatusMap[eventutil.EventTypeUpdatingPublicEndpoint])
	require.Equal(t, event.EventStatusSucceeded, clusterEventStatusMap[eventutil.EventTypeUpdatedPublicEndpoint])
	require.Equal(t, event.EventStatusStarted, clusterEventStatusMap[eventutil.EventTypeUpdatingCapacity])
	require.Equal(t, event.EventStatusSucceeded, clusterEventStatusMap[eventutil.EventTypeUpdatedCapacity])
	require.Equal(t, event.EventStatusStarted, clusterEventStatusMap[eventutil.EventTypeCreatingPrivateLink])
	require.Equal(t, event.EventStatusSucceeded, clusterEventStatusMap[eventutil.EventTypeCreatedPrivateLink])

	require.Equal(t, event.EventStatusStarted, instanceEventStatusMap[eventutil.EventTypeUpdatingPublicEndpoint])
	require.Equal(t, event.EventStatusSucceeded, instanceEventStatusMap[eventutil.EventTypeUpdatedPublicEndpoint])
	require.Equal(t, event.EventStatusStarted, instanceEventStatusMap[eventutil.EventTypeUpdatingCapacity])
	require.Equal(t, event.EventStatusSucceeded, instanceEventStatusMap[eventutil.EventTypeUpdatedCapacity])
	require.Equal(t, event.EventStatusStarted, instanceEventStatusMap[eventutil.EventTypeCreatingPrivateLink])
	require.Equal(t, event.EventStatusSucceeded, instanceEventStatusMap[eventutil.EventTypeCreatedPrivateLink])
	require.Equal(t, event.EventStatusSucceeded, clusterEventStatusMap[eventutil.EventTypeRootPasswordRotated])
	require.Equal(t, "RotateRootPassword", clusterEventTypeMap[eventutil.EventTypeRootPasswordRotated])
	require.Equal(t, "Cluster root password successfully rotated.", clusterEventSummaryMap[eventutil.EventTypeRootPasswordRotated])
}

func TestResourcePoolPauseResumeEventsAreMappedToDisplayEvents(t *testing.T) {
	tests := []struct {
		name        string
		eventType   eventutil.EventType
		displayType string
		status      event.EventStatus
	}{
		{name: "pause started", eventType: eventutil.EventTypePausing, displayType: "PauseResourcePool", status: event.EventStatusStarted},
		{name: "pause succeeded", eventType: eventutil.EventTypePaused, displayType: "PauseResourcePool", status: event.EventStatusSucceeded},
		{name: "resume started", eventType: eventutil.EventTypeResuming, displayType: "ResumeResourcePool", status: event.EventStatusStarted},
		{name: "resume succeeded", eventType: eventutil.EventTypeResumed, displayType: "ResumeResourcePool", status: event.EventStatusSucceeded},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			meta, ok := resourcePoolEventMetaMap[tt.eventType]
			require.True(t, ok)
			require.Equal(t, tt.displayType, meta.DisplayEventType)
			require.Equal(t, tt.status, meta.Status)
		})
	}
}

func TestModifyClusterVolumeEventUsesVolumeDisplayTypeAndRequestedFields(t *testing.T) {
	meta, ok := displayMetaForClusterEvent(&eventutil.ClusterEvent{
		EventType: eventutil.EventTypeScaled,
		ExtraInfo: &eventutil.EventExtraInfo{
			OperationContext: &eventutil.OperationContext{
				SourceOperation: string(eventutil.OperationSourceModifyClusterVolume),
				RequestedFields: []string{"component_type", "disk_type", "storage_iops", "additional_iops", "volume_type"},
				RequestedComponents: []*eventutil.RequestedClusterComponent{
					{
						ComponentType: globaldedicatedv1.ComponentType_TIKV.String(),
						Storages: map[string]*eventutil.RequestedStorage{
							globaldedicatedv1.Sku_DATA.String(): {
								DiskType:       globaldedicatedv1.Sku_DATA.String(),
								Iops:           9000,
								AdditionalIops: 0,
								VolumeType:     globaldedicatedv1.StorageMu_GP3.String(),
							},
						},
					},
				},
			},
		},
	})

	require.True(t, ok)
	require.Equal(t, "ModifyClusterVolume", meta.DisplayEventType)
	require.Equal(t, event.EventStatusSucceeded, meta.Status)
	expectedSummary := "Cluster volume successfully modified.\n" +
		"Requested changes: TiKV data disk: IOPS 9000, volume type gp3, additional IOPS 0."
	require.Equal(t, expectedSummary, meta.Summary)
}

func TestApplyClusterConfigEventIncludesApplyMaskDetails(t *testing.T) {
	details := renderApplyOperationDetails(&eventutil.OperationContext{
		SourceOperation: string(eventutil.OperationSourceApplyCluster),
		ApplyMask: &eventutil.ClusterApplyMask{
			Strategy:  "HOT_RELOAD",
			HotReload: true,
			ComponentApplyMasks: []*eventutil.ComponentApplyMask{
				{
					ComponentType: globaldedicatedv1.ComponentType_TIKV.String(),
					Configuration: true,
				},
				{
					ComponentType: globaldedicatedv1.ComponentType_TIDB.String(),
					Image:         true,
				},
			},
		},
	})

	require.Equal(t, "Requested changes: TiKV configuration; TiDB image; strategy HOT_RELOAD; hot reload", details)
}

func TestApplyClusterConfigEventDoesNotLeakRequestedFieldsWithoutApplyMask(t *testing.T) {
	details := renderApplyOperationDetails(&eventutil.OperationContext{
		SourceOperation: string(eventutil.OperationSourceApplyCluster),
		RequestedFields: []string{"storage_iops", "component_type"},
	})

	require.Empty(t, details)
}

func TestAppliedClusterConfigEventUsesCleanSummaryWithoutDetails(t *testing.T) {
	summary := renderApplyClusterSummary(eventutil.EventTypeApplied, &eventutil.EventExtraInfo{
		OperationContext: &eventutil.OperationContext{
			SourceOperation: string(eventutil.OperationSourceApplyCluster),
		},
	})

	require.Equal(t, "Cluster configuration successfully applied.", summary)
	require.NotContains(t, summary, "or version")
}

func TestApplyClusterUpgradeEventIncludesApplyMaskDetails(t *testing.T) {
	oldVersion := "v8.5.0"
	newVersion := "v8.5.1"

	tests := []struct {
		name      string
		eventType eventutil.EventType
		want      string
	}{
		{
			name:      "applying upgrade",
			eventType: eventutil.EventTypeApplying,
			want: "Start to upgrade cluster from version v8.5.0 to v8.5.1.\n" +
				"Requested changes: TiKV configuration; strategy ROLLING_RESTART.",
		},
		{
			name:      "applied upgrade",
			eventType: eventutil.EventTypeApplied,
			want: "Cluster successfully upgraded from version v8.5.0 to v8.5.1.\n" +
				"Requested changes: TiKV configuration; strategy ROLLING_RESTART.",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			summary := renderApplyClusterSummary(tt.eventType, &eventutil.EventExtraInfo{
				OldVersion: &oldVersion,
				NewVersion: &newVersion,
				OperationContext: &eventutil.OperationContext{
					SourceOperation: string(eventutil.OperationSourceApplyCluster),
					ApplyMask: &eventutil.ClusterApplyMask{
						Strategy: "ROLLING_RESTART",
						ComponentApplyMasks: []*eventutil.ComponentApplyMask{
							{
								ComponentType: globaldedicatedv1.ComponentType_TIKV.String(),
								Configuration: true,
							},
						},
					},
				},
			})

			require.Equal(t, tt.want, summary)
		})
	}
}

func TestConvertReservedTemplateTopology(t *testing.T) {
	topology := convertReservedTemplateTopology(&globalngv1.FixedTopology{
		Components: []*globalngv1.ComponentTopology{
			{
				ComponentType: globalngv1.ComponentType_TIDB,
				NodeQuota: &globalngv1.NodeQuota{
					NodeSpec:  &globalngv1.NodeSpec{Cpu: 8, MemoryGi: 32},
					NodeCount: 2,
				},
			},
			{
				ComponentType: globalngv1.ComponentType_TIKV,
				NodeQuota: &globalngv1.NodeQuota{
					NodeSpec:  &globalngv1.NodeSpec{Cpu: 16, MemoryGi: 64},
					NodeCount: 3,
				},
				Storages: []*globalngv1.StorageSpec{
					{Name: globalngv1.StorageSpec_DATA, Type: "gp3", SizeGi: 500, Iops: 3000},
					{Name: globalngv1.StorageSpec_RAFTLOG, Type: "io2", Iops: 1000},
				},
			},
			{
				ComponentType: globalngv1.ComponentType_PD,
				NodeQuota: &globalngv1.NodeQuota{
					NodeSpec:  &globalngv1.NodeSpec{Cpu: 4, MemoryGi: 16},
					NodeCount: 3,
				},
			},
		},
	})

	require.NotNil(t, topology)
	require.Len(t, topology.Components, 2)

	tidb := topology.Components[globaldedicatedv1.ComponentType_TIDB.String()]
	require.NotNil(t, tidb)
	require.Equal(t, uint32(2), tidb.Quantity)
	require.Equal(t, "8C32G", tidb.SpuName)

	tikv := topology.Components[globaldedicatedv1.ComponentType_TIKV.String()]
	require.NotNil(t, tikv)
	require.Equal(t, uint32(3), tikv.Quantity)
	require.Equal(t, "16C64G", tikv.SpuName)
	require.Equal(t, uint32(500), tikv.StorageSizeGi)
	require.Equal(t, "gp3", tikv.VolumeType)
	require.Equal(t, uint32(3000), tikv.AdditionalIops)
	require.Equal(t, "io2", tikv.RaftLogVolumeType)
	require.Equal(t, uint32(1000), tikv.RaftLogIops)
}

func TestLoadTopologyFromReservedCapacityTemplate(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockNextGen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mgr := &clusterEventConsumerManagerImp{nextgenCli: mockNextGen}

	cluster := &entity.Cluster{
		DeployType: entity.DeployTypeNextGen,
		GlobalCluster: &globalngv1.Cluster{
			ReservedCapacityTemplateId: 123,
		},
	}

	mockNextGen.EXPECT().GetReservedCapacityTemplate(gomock.Any(), &globalngv1.GetReservedCapacityTemplateRequest{
		CapacityTemplateId: 123,
	}).Return(&globalngv1.ReservedCapacityTemplate{
		FixedTopology: &globalngv1.FixedTopology{Components: []*globalngv1.ComponentTopology{
			{
				ComponentType: globalngv1.ComponentType_TIFLASH_WRITE,
				NodeQuota: &globalngv1.NodeQuota{
					NodeSpec:  &globalngv1.NodeSpec{Cpu: 4, MemoryGi: 16},
					NodeCount: 1,
				},
			},
		}},
	}, nil)

	topology := mgr.loadTopologyFromReservedCapacityTemplate(context.Background(), cluster, &eventutil.ClusterEvent{ClusterID: 1})
	require.NotNil(t, topology)
	require.NotNil(t, topology.Components[globaldedicatedv1.ComponentType_TIFLASH.String()])
}

func TestConvertReservedTemplateTopology_MergesTiFlashWriteAndCompute(t *testing.T) {
	topology := convertReservedTemplateTopology(&globalngv1.FixedTopology{
		Components: []*globalngv1.ComponentTopology{
			{
				ComponentType: globalngv1.ComponentType_TIFLASH_WRITE,
				NodeQuota: &globalngv1.NodeQuota{
					NodeSpec:  &globalngv1.NodeSpec{Cpu: 8, MemoryGi: 32},
					NodeCount: 2,
				},
				Storages: []*globalngv1.StorageSpec{{
					Name:   globalngv1.StorageSpec_DATA,
					Type:   "gp3",
					SizeGi: 500,
					Iops:   3000,
				}},
			},
			{
				ComponentType: globalngv1.ComponentType_TIFLASH_COMPUTE,
				NodeQuota: &globalngv1.NodeQuota{
					NodeSpec:  &globalngv1.NodeSpec{Cpu: 8, MemoryGi: 32},
					NodeCount: 3,
				},
			},
		},
	})

	require.NotNil(t, topology)
	tiflash := topology.Components[globaldedicatedv1.ComponentType_TIFLASH.String()]
	require.NotNil(t, tiflash)
	require.Equal(t, uint32(5), tiflash.Quantity)
	require.Equal(t, "8C32G", tiflash.SpuName)
	require.Equal(t, uint32(500), tiflash.StorageSizeGi)
	require.Equal(t, "gp3", tiflash.VolumeType)
}
