package utils

import (
	"testing"

	dedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	openapiv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"google.golang.org/protobuf/types/known/timestamppb"

	"github.com/pingcap-inc/clinic/pkg/db"
)

func TestDedicatedComponentToView(t *testing.T) {
	count := uint32(3)
	volumeType := dedicatedv1.StorageMu_GP3
	components := DedicatedComponentToView([]*dedicatedv1.Cluster_ClusterComponent{
		{
			Id:             42,
			ComponentType:  dedicatedv1.ComponentType_TIKV,
			Count:          &count,
			CpuNum:         8,
			MemorySizeGi:   32,
			SpuVersion:     "spu-v1",
			SpuDisplayName: "ignored-display-name",
			Storages: map[string]*dedicatedv1.Cluster_ClusterComponent_Storage{
				"data": {
					VolumeType:      &volumeType,
					DiskType:        dedicatedv1.Sku_DATA,
					SizeGi:          500,
					Iops:            3000,
					ThroughputMibps: 125,
				},
			},
		},
		nil,
		{
			Id:            43,
			ComponentType: dedicatedv1.ComponentType_TIDB,
			DeleteTime:    timestamppb.Now(),
		},
	}, &dedicatedv1.ClusterConfiguration{
		ComponentConfigurations: []*dedicatedv1.ClusterConfiguration_ComponentConfiguration{
			{
				ComponentType:     dedicatedv1.ComponentType_TIKV,
				OverwriteImageTag: "v8.5.4-20251209-34c6617",
			},
		},
	}, map[string]string{"tikv": "v8.5.4-effective"})

	if len(components) != 1 {
		t.Fatalf("expected 1 active component, got %d", len(components))
	}

	component := components[0]
	if component.Id != "42" {
		t.Fatalf("expected id 42, got %s", component.Id)
	}
	expectedComponentType := dedicatedv1.ComponentType_TIKV.String()
	if string(component.ComponentType) != expectedComponentType {
		t.Fatalf("expected component type %s, got %s", expectedComponentType, component.ComponentType)
	}
	if component.NodeCount != count || component.MinNodeCount != count || component.MaxNodeCount != count {
		t.Fatalf("expected fixed node count %d, got node=%d min=%d max=%d", count, component.NodeCount, component.MinNodeCount, component.MaxNodeCount)
	}
	if component.CpuNum != 8 || component.MemoryGi != 32 {
		t.Fatalf("expected 8C32G, got cpu=%d memory=%d", component.CpuNum, component.MemoryGi)
	}
	if component.Image == nil || component.Image.Tag != "v8.5.4-effective" {
		t.Fatalf("expected image tag v8.5.4-effective, got %#v", component.Image)
	}
	if len(component.Storages) != 1 {
		t.Fatalf("expected 1 storage, got %d", len(component.Storages))
	}
	storage := component.Storages[0]
	if storage.Name != "data" {
		t.Fatalf("expected storage name data, got %s", storage.Name)
	}
	if storage.Type != volumeType.String() {
		t.Fatalf("expected storage type %s, got %s", volumeType.String(), storage.Type)
	}
	if storage.SizeGi != 500 || storage.Iops != 3000 || storage.ThroughputMbs != 125 {
		t.Fatalf("unexpected storage values: size=%d iops=%d throughput=%d", storage.SizeGi, storage.Iops, storage.ThroughputMbs)
	}
}

func TestDedicatedComponentToViewFallsBackToOverwriteImageTag(t *testing.T) {
	count := uint32(3)
	components := DedicatedComponentToView([]*dedicatedv1.Cluster_ClusterComponent{
		{
			Id:            42,
			ComponentType: dedicatedv1.ComponentType_TIKV,
			Count:         &count,
		},
	}, &dedicatedv1.ClusterConfiguration{
		ComponentConfigurations: []*dedicatedv1.ClusterConfiguration_ComponentConfiguration{
			{
				ComponentType:     dedicatedv1.ComponentType_TIKV,
				OverwriteImageTag: "v8.5.4-20251209-34c6617",
			},
		},
	}, nil)

	if len(components) != 1 {
		t.Fatalf("expected 1 active component, got %d", len(components))
	}
	if components[0].Image == nil || components[0].Image.Tag != "v8.5.4-20251209-34c6617" {
		t.Fatalf("expected overwrite image tag fallback, got %#v", components[0].Image)
	}
}

func TestDedicatedComponentToViewOmitsEmptyImageTag(t *testing.T) {
	count := uint32(3)
	components := DedicatedComponentToView([]*dedicatedv1.Cluster_ClusterComponent{
		{
			Id:            42,
			ComponentType: dedicatedv1.ComponentType_TIKV,
			Count:         &count,
		},
	}, nil, nil)

	if len(components) != 1 {
		t.Fatalf("expected 1 active component, got %d", len(components))
	}
	if components[0].Image != nil {
		t.Fatalf("expected no image tag, got %#v", components[0].Image)
	}
}

func TestDedicatedStorageTypeFallsBackToDiskType(t *testing.T) {
	storageType := dedicatedStorageType(&dedicatedv1.Cluster_ClusterComponent_Storage{
		DiskType: dedicatedv1.Sku_RAFT_LOG,
	})
	if storageType != dedicatedv1.Sku_RAFT_LOG.String() {
		t.Fatalf("expected disk type fallback %s, got %s", dedicatedv1.Sku_RAFT_LOG.String(), storageType)
	}
}

func TestTopologyFromDedicatedComponents(t *testing.T) {
	tidbDefaultCount := uint32(7)
	tidbAnalyticsCount := uint32(3)
	tikvCount := uint32(21)
	pdCount := uint32(3)
	removedTiFlashCount := uint32(3)

	topology := topologyFromDedicatedComponents([]*dedicatedv1.Cluster_ClusterComponent{
		{ComponentType: dedicatedv1.ComponentType_TIDB, Count: &tidbDefaultCount},
		{ComponentType: dedicatedv1.ComponentType_TIDB, Count: &tidbAnalyticsCount},
		{ComponentType: dedicatedv1.ComponentType_TIKV, Count: &tikvCount},
		{ComponentType: dedicatedv1.ComponentType_PD, Count: &pdCount},
		{ComponentType: dedicatedv1.ComponentType_TIFLASH, Count: &removedTiFlashCount, DeleteTime: timestamppb.Now()},
		nil,
	})

	if topology.TiDB != 10 || topology.TiKV != 21 || topology.PD != 3 || topology.TiFlash != 0 {
		t.Fatalf("unexpected topology: %#v", topology)
	}

	emptyTopology := topologyFromDedicatedComponents(nil)
	if emptyTopology == nil || emptyTopology.TiDB != 0 || emptyTopology.TiKV != 0 || emptyTopology.PD != 0 || emptyTopology.TiFlash != 0 {
		t.Fatalf("unexpected empty topology: %#v", emptyTopology)
	}
}

func TestClusterSyncDiffForTopology(t *testing.T) {
	topology := func(tidb int) *db.Topology {
		return &db.Topology{TiDB: tidb, TiKV: 21, PD: 3, TiFlash: 7}
	}

	tests := []struct {
		name     string
		existing db.Cluster
		desired  db.Cluster
		want     bool
	}{
		{
			name:     "does not update when desired topology is absent",
			existing: db.Cluster{Topology: topology(12)},
			desired:  db.Cluster{},
			want:     false,
		},
		{
			name:     "does not update when topology is unchanged",
			existing: db.Cluster{Topology: topology(12)},
			desired:  db.Cluster{Topology: topology(12)},
			want:     false,
		},
		{
			name:     "updates when cached topology is absent",
			existing: db.Cluster{},
			desired:  db.Cluster{Topology: topology(12)},
			want:     true,
		},
		{
			name:     "updates when a replica count changes",
			existing: db.Cluster{Topology: topology(12)},
			desired:  db.Cluster{Topology: topology(32)},
			want:     true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := ClusterSyncDiff(tt.existing, tt.desired); got != tt.want {
				t.Fatalf("ClusterSyncDiff() = %t, want %t", got, tt.want)
			}
		})
	}
}

func TestGetClusterStatusWithoutOverrideIgnoresCustomStatus(t *testing.T) {
	overrideStatus := openapiv1.ClusterState_RESTORING
	cluster := &mgmtv1.Cluster{
		ClusterId: 10204581922395763238,
		CustomStatuses: []*mgmtv1.ClusterCustomStatus{
			{
				OverrideStatus: &overrideStatus,
			},
		},
	}

	if got := GetClusterStatus(cluster); got != openapiv1.ClusterState_RESTORING {
		t.Fatalf("expected overridden status RESTORING, got %s", got)
	}
	if got := GetClusterStatusWithoutOverride(cluster); got != openapiv1.ClusterState_CLUSTER_STATE_UNSPECIFIED {
		t.Fatalf("expected base status CLUSTER_STATE_UNSPECIFIED, got %s", got)
	}
}
