package server

import (
	"context"
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
)

func TestFinalizeStablePDDeploymentModeSharedPoolToMicroservice(t *testing.T) {
	sharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Phase:            apiv1.SharedPool_AVAILABLE,
			PdDeploymentMode: apiv1.PDDeploymentMode_PD_DEPLOYMENT_MODE_MICROSERVICE,
			ComponentGroups: []*entity.Component{
				{Id: "default", ComponentType: apiv1.ComponentType_PD, Phase: apiv1.Component_AVAILABLE},
				{Id: "default", ComponentType: apiv1.ComponentType_TIDB_WORKER, Phase: apiv1.Component_AVAILABLE},
			},
		},
	}
	targetSharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			ComponentGroups: []*entity.Component{
				{Id: "default", ComponentType: apiv1.ComponentType_PD, Phase: apiv1.Component_MODIFYING},
				{Id: "default", ComponentType: apiv1.ComponentType_PD_TSO, Phase: apiv1.Component_MODIFYING},
				{Id: "default", ComponentType: apiv1.ComponentType_PD_SCHEDULING, Phase: apiv1.Component_MODIFYING},
				{Id: "default", ComponentType: apiv1.ComponentType_RESOURCE_MANAGER, Phase: apiv1.Component_MODIFYING},
				{Id: "default", ComponentType: apiv1.ComponentType_TIDB_WORKER, Phase: apiv1.Component_DELETING},
			},
		},
	}

	finalizeStablePDDeploymentModeSharedPool(sharedPool, targetSharedPool, apiv1.PDDeploymentMode_PD_DEPLOYMENT_MODE_MICROSERVICE)

	assert.Equal(t, apiv1.SharedPool_AVAILABLE, sharedPool.GetPhase())
	assert.Equal(t, apiv1.PDDeploymentMode_PD_DEPLOYMENT_MODE_MICROSERVICE, sharedPool.GetPdDeploymentMode())
	require.NotNil(t, sharedPool.GetPdDeploymentStatus())
	assert.Equal(t, apiv1.PDDeploymentStatusPhase_PD_DEPLOYMENT_STATUS_PHASE_STABLE, sharedPool.GetPdDeploymentStatus().GetPhase())
	assert.Equal(t, apiv1.Component_AVAILABLE, requireServerTestComponentByType(t, sharedPool.GetComponentGroups(), apiv1.ComponentType_PD).GetPhase())
	assert.Equal(t, apiv1.Component_AVAILABLE, requireServerTestComponentByType(t, sharedPool.GetComponentGroups(), apiv1.ComponentType_PD_TSO).GetPhase())
	assert.Equal(t, apiv1.Component_AVAILABLE, requireServerTestComponentByType(t, sharedPool.GetComponentGroups(), apiv1.ComponentType_PD_SCHEDULING).GetPhase())
	assert.Equal(t, apiv1.Component_AVAILABLE, requireServerTestComponentByType(t, sharedPool.GetComponentGroups(), apiv1.ComponentType_RESOURCE_MANAGER).GetPhase())
	assert.Equal(t, apiv1.Component_AVAILABLE, requireServerTestComponentByType(t, sharedPool.GetComponentGroups(), apiv1.ComponentType_TIDB_WORKER).GetPhase())
}

func TestFinalizeStablePDDeploymentModeSharedPoolToMonolithic(t *testing.T) {
	sharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Phase:            apiv1.SharedPool_AVAILABLE,
			PdDeploymentMode: apiv1.PDDeploymentMode_PD_DEPLOYMENT_MODE_MONOLITHIC,
			ComponentGroups: []*entity.Component{
				{Id: "default", ComponentType: apiv1.ComponentType_PD, Phase: apiv1.Component_MODIFYING},
				{Id: "default", ComponentType: apiv1.ComponentType_PD_TSO, Phase: apiv1.Component_DELETING},
				{Id: "default", ComponentType: apiv1.ComponentType_PD_SCHEDULING, Phase: apiv1.Component_DELETING},
				{Id: "default", ComponentType: apiv1.ComponentType_RESOURCE_MANAGER, Phase: apiv1.Component_DELETING},
				{Id: "default", ComponentType: apiv1.ComponentType_TIDB_WORKER, Phase: apiv1.Component_AVAILABLE},
			},
		},
	}
	targetSharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			ComponentGroups: sharedPool.GetComponentGroups(),
		},
	}

	finalizeStablePDDeploymentModeSharedPool(sharedPool, targetSharedPool, apiv1.PDDeploymentMode_PD_DEPLOYMENT_MODE_MONOLITHIC)

	assert.Equal(t, apiv1.SharedPool_AVAILABLE, sharedPool.GetPhase())
	assert.Equal(t, apiv1.PDDeploymentMode_PD_DEPLOYMENT_MODE_MONOLITHIC, sharedPool.GetPdDeploymentMode())
	require.NotNil(t, sharedPool.GetPdDeploymentStatus())
	assert.Equal(t, apiv1.PDDeploymentStatusPhase_PD_DEPLOYMENT_STATUS_PHASE_STABLE, sharedPool.GetPdDeploymentStatus().GetPhase())
	assert.NotNil(t, findServerTestComponentByType(sharedPool.GetComponentGroups(), apiv1.ComponentType_PD))
	assert.NotNil(t, findServerTestComponentByType(sharedPool.GetComponentGroups(), apiv1.ComponentType_TIDB_WORKER))
	assert.Nil(t, findServerTestComponentByType(sharedPool.GetComponentGroups(), apiv1.ComponentType_PD_TSO))
	assert.Nil(t, findServerTestComponentByType(sharedPool.GetComponentGroups(), apiv1.ComponentType_PD_SCHEDULING))
	assert.Nil(t, findServerTestComponentByType(sharedPool.GetComponentGroups(), apiv1.ComponentType_RESOURCE_MANAGER))
}

func TestBuildPDDeploymentModeRunningSharedPoolPreservesCurrentNonPDComponents(t *testing.T) {
	sharedPool := &entity.SharedPool{
		TenantId:                  "tenant-123",
		ConfigCenterMetaProfileId: "1001",
		SharedPool: apiv1.SharedPool{
			Phase:            apiv1.SharedPool_AVAILABLE,
			PdDeploymentMode: apiv1.PDDeploymentMode_PD_DEPLOYMENT_MODE_MONOLITHIC,
			ComponentGroups: []*entity.Component{
				{Id: "default", ComponentType: apiv1.ComponentType_PD, Phase: apiv1.Component_AVAILABLE},
				{Id: "default", ComponentType: apiv1.ComponentType_TIDB_WORKER, Phase: apiv1.Component_AVAILABLE, NodeQuota: &apiv1.NodeQuota{NodeCount: 1}},
				{Id: "default", ComponentType: apiv1.ComponentType_TIPROXY, Phase: apiv1.Component_AVAILABLE, NodeQuota: &apiv1.NodeQuota{NodeCount: 2}},
			},
		},
	}
	targetSharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Phase:            apiv1.SharedPool_MODIFYING,
			PdDeploymentMode: apiv1.PDDeploymentMode_PD_DEPLOYMENT_MODE_MICROSERVICE,
			ComponentGroups: []*entity.Component{
				{Id: "default", ComponentType: apiv1.ComponentType_PD, Phase: apiv1.Component_MODIFYING},
				{Id: "default", ComponentType: apiv1.ComponentType_PD_TSO, Phase: apiv1.Component_MODIFYING},
				{Id: "default", ComponentType: apiv1.ComponentType_PD_SCHEDULING, Phase: apiv1.Component_MODIFYING},
				{Id: "default", ComponentType: apiv1.ComponentType_RESOURCE_MANAGER, Phase: apiv1.Component_MODIFYING},
				{Id: "default", ComponentType: apiv1.ComponentType_TIDB_WORKER, Phase: apiv1.Component_DELETING, NodeQuota: &apiv1.NodeQuota{NodeCount: 9}},
				{Id: "default", ComponentType: apiv1.ComponentType_TIPROXY, Phase: apiv1.Component_MODIFYING, NodeQuota: &apiv1.NodeQuota{NodeCount: 10}},
			},
		},
	}

	got := buildPDDeploymentModeRunningSharedPool(sharedPool, targetSharedPool, apiv1.PDDeploymentMode_PD_DEPLOYMENT_MODE_MICROSERVICE)

	assert.Equal(t, apiv1.SharedPool_MODIFYING, got.GetPhase())
	assert.Equal(t, "tenant-123", got.TenantId)
	assert.Equal(t, "1001", got.ConfigCenterMetaProfileId)
	assert.Equal(t, apiv1.PDDeploymentMode_PD_DEPLOYMENT_MODE_MICROSERVICE, got.GetPdDeploymentMode())
	assert.Equal(t, apiv1.Component_MODIFYING, requireServerTestComponentByType(t, got.GetComponentGroups(), apiv1.ComponentType_PD).GetPhase())
	assert.Equal(t, apiv1.Component_MODIFYING, requireServerTestComponentByType(t, got.GetComponentGroups(), apiv1.ComponentType_PD_TSO).GetPhase())
	assert.Equal(t, apiv1.Component_MODIFYING, requireServerTestComponentByType(t, got.GetComponentGroups(), apiv1.ComponentType_PD_SCHEDULING).GetPhase())
	assert.Equal(t, apiv1.Component_MODIFYING, requireServerTestComponentByType(t, got.GetComponentGroups(), apiv1.ComponentType_RESOURCE_MANAGER).GetPhase())

	tidbWorker := requireServerTestComponentByType(t, got.GetComponentGroups(), apiv1.ComponentType_TIDB_WORKER)
	assert.Equal(t, apiv1.Component_AVAILABLE, tidbWorker.GetPhase())
	assert.Equal(t, uint32(1), tidbWorker.GetNodeQuota().GetNodeCount())
	tiproxy := requireServerTestComponentByType(t, got.GetComponentGroups(), apiv1.ComponentType_TIPROXY)
	assert.Equal(t, apiv1.Component_AVAILABLE, tiproxy.GetPhase())
	assert.Equal(t, uint32(2), tiproxy.GetNodeQuota().GetNodeCount())
}

func TestBuildPDDeploymentModeTargetSharedPoolPreservesTenantID(t *testing.T) {
	sharedPool := &entity.SharedPool{
		TenantId:                  "tenant-123",
		ConfigCenterMetaProfileId: "1001",
		SharedPool: apiv1.SharedPool{
			Phase: apiv1.SharedPool_AVAILABLE,
			ComponentGroups: []*entity.Component{
				{Id: "default", ComponentType: apiv1.ComponentType_PD, Phase: apiv1.Component_AVAILABLE},
			},
		},
	}

	got, err := (&serverImpl{}).buildPDDeploymentModeTargetSharedPool(
		context.Background(),
		sharedPool,
		apiv1.PDDeploymentMode_PD_DEPLOYMENT_MODE_MONOLITHIC,
	)

	require.NoError(t, err)
	assert.Equal(t, "tenant-123", got.TenantId)
	assert.Equal(t, "1001", got.ConfigCenterMetaProfileId)
}

func TestPDDeploymentModeExecIDModeNameIsRFC1123Safe(t *testing.T) {
	tests := []struct {
		name string
		mode entity.PDDeploymentMode
		want string
	}{
		{
			name: "monolithic",
			mode: apiv1.PDDeploymentMode_PD_DEPLOYMENT_MODE_MONOLITHIC,
			want: "monolithic",
		},
		{
			name: "microservice",
			mode: apiv1.PDDeploymentMode_PD_DEPLOYMENT_MODE_MICROSERVICE,
			want: "microservice",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := pdDeploymentModeExecIDModeName(tt.mode)
			require.Equal(t, tt.want, got)

			execIDPrefix := fmt.Sprintf("set-pd-deployment-mode-%s-%s", "2074671845063262208", got)
			require.Regexp(t, "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", execIDPrefix)
		})
	}
}

func requireServerTestComponentByType(t *testing.T, components []*entity.Component, componentType entity.ComponentType) *entity.Component {
	t.Helper()

	component := findServerTestComponentByType(components, componentType)
	require.NotNil(t, component)
	return component
}

func findServerTestComponentByType(components []*entity.Component, componentType entity.ComponentType) *entity.Component {
	for _, comp := range components {
		if comp.GetComponentType() == componentType {
			return comp
		}
	}
	return nil
}
