package cluster

import (
	"testing"
	"time"

	"google.golang.org/protobuf/proto"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
)

func TestCvtFilterToQueryDeleted(t *testing.T) {
	t.Parallel()

	t.Run("nil filter", func(t *testing.T) {
		if got := cvtFilterToQueryDeleted(nil); !got {
			t.Errorf("cvtFilterToQueryDeleted() = %v, want %v", got, true)
		}
	})

	t.Run("filter with deleted set to true", func(t *testing.T) {
		filter := &tidbmgmtservicev1.ClusterFilter{
			Deleted: proto.Bool(true),
		}
		if got := cvtFilterToQueryDeleted(filter); !got {
			t.Errorf("cvtFilterToQueryDeleted() = %v, want %v", got, true)
		}
	})

	t.Run("filter with deleted set to false", func(t *testing.T) {
		filter := &tidbmgmtservicev1.ClusterFilter{
			Deleted: proto.Bool(false),
		}
		if got := cvtFilterToQueryDeleted(filter); got {
			t.Errorf("cvtFilterToQueryDeleted() = %v, want %v", got, false)
		}
	})

	t.Run("filter with states containing DELETED", func(t *testing.T) {
		filter := &tidbmgmtservicev1.ClusterFilter{
			States: []openapicommonv1beta1.ClusterState{openapicommonv1beta1.ClusterState_DELETED},
		}
		if got := cvtFilterToQueryDeleted(filter); !got {
			t.Errorf("cvtFilterToQueryDeleted() = %v, want %v", got, true)
		}
	})

	t.Run("filter with states not containing DELETED or DELETING", func(t *testing.T) {
		filter := &tidbmgmtservicev1.ClusterFilter{
			States: []openapicommonv1beta1.ClusterState{openapicommonv1beta1.ClusterState_CREATING},
		}
		if got := cvtFilterToQueryDeleted(filter); got {
			t.Errorf("cvtFilterToQueryDeleted() = %v, want %v", got, false)
		}
	})

	t.Run("filter with excludeStates containing DELETED", func(t *testing.T) {
		filter := &tidbmgmtservicev1.ClusterFilter{
			ExcludeStates: []openapicommonv1beta1.ClusterState{openapicommonv1beta1.ClusterState_DELETED},
		}
		if got := cvtFilterToQueryDeleted(filter); got {
			t.Errorf("cvtFilterToQueryDeleted() = %v, want %v", got, false)
		}
	})

	t.Run("filter with cluster id and states not containing DELETED or DELETING", func(t *testing.T) {
		filter := &tidbmgmtservicev1.ClusterFilter{
			ClusterIds: []uint64{1},
		}
		if got := cvtFilterToQueryDeleted(filter); !got {
			t.Errorf("cvtFilterToQueryDeleted() = %v, want %v", got, true)
		}
	})

	t.Run("filter with cluster id ", func(t *testing.T) {
		filter := &tidbmgmtservicev1.ClusterFilter{
			ClusterIds: []uint64{1},
			States:     []openapicommonv1beta1.ClusterState{openapicommonv1beta1.ClusterState_ACTIVE},
		}
		if got := cvtFilterToQueryDeleted(filter); got {
			t.Errorf("cvtFilterToQueryDeleted() = %v, want %v", got, false)
		}
	})

	t.Run("filter with cluster id and excludeStates containing DELETED", func(t *testing.T) {
		filter := &tidbmgmtservicev1.ClusterFilter{
			ClusterIds:    []uint64{1},
			ExcludeStates: []openapicommonv1beta1.ClusterState{openapicommonv1beta1.ClusterState_DELETED},
		}
		if got := cvtFilterToQueryDeleted(filter); got {
			t.Errorf("cvtFilterToQueryDeleted() = %v, want %v", got, false)
		}
	})

	t.Run("filter with cluster id", func(t *testing.T) {
		filter := &tidbmgmtservicev1.ClusterFilter{
			ClusterIds: []uint64{1},
		}
		if got := cvtFilterToQueryDeleted(filter); !got {
			t.Errorf("cvtFilterToQueryDeleted() = %v, want %v", got, true)
		}
	})

	t.Run("filter with cluster id", func(t *testing.T) {
		filter := &tidbmgmtservicev1.ClusterFilter{
			ClusterIds: []uint64{1},
			States:     []openapicommonv1beta1.ClusterState{openapicommonv1beta1.ClusterState_DELETED},
		}
		if got := cvtFilterToQueryDeleted(filter); !got {
			t.Errorf("cvtFilterToQueryDeleted() = %v, want %v", got, true)
		}
	})
}

func TestCvtToMgmtCluster(t *testing.T) {
	t.Parallel()

	t.Run("nil cluster", func(t *testing.T) {
		if got := cvtToMgmtCluster(nil, nil); got != nil {
			t.Errorf("cvtToMgmtCluster(nil, nil) = %v, want nil", got)
		}
	})

	t.Run("cluster with non-zero OriginalProjectId", func(t *testing.T) {
		cluster := &entity.Cluster{
			ClusterId:         123,
			ProjectId:         456,
			OriginalProjectId: 789,
			OrganizationId:    111,
			DisplayName:       "test-cluster",
			CloudProvider:     openapicommonv1beta1.Region_aws,
			Region:            "us-west-2",
			UserId:            "user-123",
			DeployType:        entity.DeployTypeServerless,
			CreatedAt:         time.Now(),
			UpdatedAt:         time.Now(),
		}

		result := cvtToMgmtCluster(cluster, nil)

		if result == nil {
			t.Fatal("cvtToMgmtCluster() returned nil, expected non-nil result")
		}

		if result.ClusterId != cluster.ClusterId {
			t.Errorf("ClusterId = %v, want %v", result.ClusterId, cluster.ClusterId)
		}

		if result.ProjectId != cluster.ProjectId {
			t.Errorf("ProjectId = %v, want %v", result.ProjectId, cluster.ProjectId)
		}

		if result.OriginalProjectId != cluster.OriginalProjectId {
			t.Errorf("OriginalProjectId = %v, want %v", result.OriginalProjectId, cluster.OriginalProjectId)
		}
	})

	t.Run("cluster with zero OriginalProjectId falls back to ProjectId", func(t *testing.T) {
		cluster := &entity.Cluster{
			ClusterId:         123,
			ProjectId:         456,
			OriginalProjectId: 0, // zero value
			OrganizationId:    111,
			DisplayName:       "test-cluster",
			CloudProvider:     openapicommonv1beta1.Region_aws,
			Region:            "us-west-2",
			UserId:            "user-123",
			DeployType:        entity.DeployTypeServerless,
			CreatedAt:         time.Now(),
			UpdatedAt:         time.Now(),
		}

		result := cvtToMgmtCluster(cluster, nil)

		if result == nil {
			t.Fatal("cvtToMgmtCluster() returned nil, expected non-nil result")
		}

		if result.ClusterId != cluster.ClusterId {
			t.Errorf("ClusterId = %v, want %v", result.ClusterId, cluster.ClusterId)
		}

		if result.ProjectId != cluster.ProjectId {
			t.Errorf("ProjectId = %v, want %v", result.ProjectId, cluster.ProjectId)
		}

		// When OriginalProjectId is 0, it should fall back to ProjectId
		if result.OriginalProjectId != cluster.ProjectId {
			t.Errorf("OriginalProjectId = %v, want %v (fallback to ProjectId)", result.OriginalProjectId, cluster.ProjectId)
		}
	})

	t.Run("dedicated cluster embedded project_id is overridden by authoritative value", func(t *testing.T) {
		staleProjectId := uint64(999)
		authoritativeProjectId := uint64(456)
		dedicatedGlobal := &globaldedicatedv1.Cluster{
			ClusterId: 123,
			ProjectId: staleProjectId, // stale value from global service
		}
		cluster := &entity.Cluster{
			ClusterId:      123,
			ProjectId:      authoritativeProjectId,
			OrganizationId: 111,
			DeployType:     entity.DeployTypeDedicated,
			GlobalCluster:  dedicatedGlobal,
			CreatedAt:      time.Now(),
			UpdatedAt:      time.Now(),
		}

		result := cvtToMgmtCluster(cluster, nil)

		if result == nil {
			t.Fatal("cvtToMgmtCluster() returned nil, expected non-nil result")
		}
		if result.ProjectId != authoritativeProjectId {
			t.Errorf("ProjectId = %v, want %v", result.ProjectId, authoritativeProjectId)
		}
		// The embedded dedicated cluster's project_id must also be overridden.
		if result.GetDedicatedCluster().GetProjectId() != authoritativeProjectId {
			t.Errorf("DedicatedCluster.ProjectId = %v, want %v",
				result.GetDedicatedCluster().GetProjectId(), authoritativeProjectId)
		}
	})

	t.Run("serverless cluster embedded project_id is overridden by authoritative value", func(t *testing.T) {
		staleProjectId := uint64(999)
		authoritativeProjectId := uint64(456)
		serverlessGlobal := &globalserverlessv1.ServerlessServiceCluster{
			ClusterId: "123",
			ProjectId: staleProjectId, // stale value from global service
		}
		cluster := &entity.Cluster{
			ClusterId:      123,
			ProjectId:      authoritativeProjectId,
			OrganizationId: 111,
			DeployType:     entity.DeployTypeServerless,
			GlobalCluster:  serverlessGlobal,
			CreatedAt:      time.Now(),
			UpdatedAt:      time.Now(),
		}

		result := cvtToMgmtCluster(cluster, nil)

		if result == nil {
			t.Fatal("cvtToMgmtCluster() returned nil, expected non-nil result")
		}
		if result.ProjectId != authoritativeProjectId {
			t.Errorf("ProjectId = %v, want %v", result.ProjectId, authoritativeProjectId)
		}
		// The embedded serverless cluster's project_id must also be overridden.
		if result.GetServerlessCluster().GetProjectId() != authoritativeProjectId {
			t.Errorf("ServerlessCluster.ProjectId = %v, want %v",
				result.GetServerlessCluster().GetProjectId(), authoritativeProjectId)
		}
	})

	t.Run("serverless cluster keeps authoritative service plan when global auto scaling is absent", func(t *testing.T) {
		cluster := &entity.Cluster{
			ClusterId:      123,
			ProjectId:      456,
			OrganizationId: 111,
			DeployType:     entity.DeployTypeServerless,
			GlobalCluster: &globalserverlessv1.ServerlessServiceCluster{
				ClusterId: "123",
			},
			ServicePlan: openapicommonv1beta1.ServicePlan_Essential,
			CreatedAt:   time.Now(),
			UpdatedAt:   time.Now(),
		}

		result := cvtToMgmtCluster(cluster, nil)

		if result == nil {
			t.Fatal("cvtToMgmtCluster() returned nil, expected non-nil result")
		}
		if result.ServicePlan != openapicommonv1beta1.ServicePlan_Essential {
			t.Errorf("ServicePlan = %v, want %v", result.ServicePlan, openapicommonv1beta1.ServicePlan_Essential)
		}
	})
}

func TestBuildDedicatedGlobalCluster(t *testing.T) {
	t.Parallel()

	t.Run("dedicated cluster should have ServicePlan_Dedicated", func(t *testing.T) {
		cluster := &tidbmgmtservicev1.Cluster{
			ClusterName:    "test-cluster",
			ProjectId:      1,
			OrganizationId: 2,
			CloudProvider:  "aws",
			Region:         "us-east-1",
			CreatedBy:      "user@example.com",
			ClusterInfo: &tidbmgmtservicev1.Cluster_DedicatedCluster{
				DedicatedCluster: &globaldedicatedv1.Cluster{
					ClusterComponents: []*globaldedicatedv1.Cluster_ClusterComponent{
						{ComponentType: globaldedicatedv1.ComponentType_TIDB},
					},
				},
			},
		}

		result, err := buildDedicatedGlobalCluster(cluster)
		if err != nil {
			t.Fatalf("buildDedicatedGlobalCluster() unexpected error: %v", err)
		}
		if result.ServicePlan != openapicommonv1beta1.ServicePlan_Dedicated {
			t.Errorf("ServicePlan = %v, want %v", result.ServicePlan, openapicommonv1beta1.ServicePlan_Dedicated)
		}
		if result.DeployType != entity.DeployTypeDedicated {
			t.Errorf("DeployType = %v, want %v", result.DeployType, entity.DeployTypeDedicated)
		}
	})
}
