package tioperator

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func TestNewTiOperator(t *testing.T) {
	scheme := buildTestScheme(t)
	cli := fake.NewClientBuilder().WithScheme(scheme).Build()

	op := NewTiOperator(cli)
	require.NotNil(t, op)

	// Verify it returns a tiOperatorImpl instance
	impl, ok := op.(*tiOperatorImpl)
	assert.True(t, ok)
	assert.NotNil(t, impl.client)
}

func TestTiOperatorImpl_ListClusters(t *testing.T) {
	scheme := buildTestScheme(t)
	cli := fake.NewClientBuilder().WithScheme(scheme).Build()

	op := NewTiOperator(cli)
	ctx := context.Background()

	clusters, err := op.ListClusters(ctx)
	require.NoError(t, err)
	// clusters can be nil or empty when no clusters exist
	if clusters != nil {
		assert.Empty(t, clusters)
	}
}

func TestTiOperatorImpl_GetCluster(t *testing.T) {
	scheme := buildTestScheme(t)
	cli := fake.NewClientBuilder().WithScheme(scheme).Build()

	op := NewTiOperator(cli)
	ctx := context.Background()

	cluster, err := op.GetCluster(ctx, "test-cluster")
	require.NoError(t, err)
	require.NotNil(t, cluster)
	assert.Equal(t, "test-cluster", cluster.Id)
}

// TiDB Group operations
func TestTiOperatorImpl_TiDBGroupOperations(t *testing.T) {
	clusterId := "test-cluster"
	namespace := dataplaneNamespace(clusterId)
	tidbId := testComponentId("tidb")
	tidbGroup := newTiDBGroup(t, namespace, tidbId, "tidb", "tidb", 2)

	scheme := buildTestScheme(t)
	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(tidbGroup).Build()

	op := NewTiOperator(cli)
	ctx := context.Background()

	// List
	t.Run("ListTiDBGroups", func(t *testing.T) {
		components, err := op.ListTiDBGroups(ctx, clusterId)
		require.NoError(t, err)
		assert.Len(t, components, 1)
		assert.Equal(t, apiv1.ComponentType_TIDB, components[0].GetComponentType())
	})

	// Get
	t.Run("GetTiDBGroup", func(t *testing.T) {
		groupId := groupIdWithComponent(tidbId, "tidb")
		component, err := op.GetTiDBGroup(ctx, clusterId, groupId)
		require.NoError(t, err)
		require.NotNil(t, component)
		assert.Equal(t, apiv1.ComponentType_TIDB, component.GetComponentType())
	})

	// Update
	t.Run("UpdateTiDBGroup", func(t *testing.T) {
		groupId := groupIdWithComponent(tidbId, "tidb")
		options := []UpdateOption{{Option: OpsUpdateNodeCount, Value: uint32(3)}}
		updated, err := op.UpdateTiDBGroup(ctx, clusterId, groupId, options...)
		require.NoError(t, err)
		require.NotNil(t, updated)
		assert.Equal(t, uint32(3), updated.GetNodeQuota().GetNodeCount())
	})
}

// TiDB Worker operations
func TestTiOperatorImpl_TiDBWorkerOperations(t *testing.T) {
	clusterId := "test-cluster"
	namespace := dataplaneNamespace(clusterId)
	workerId := testComponentId("worker")
	workerGroup := newTiDBGroup(t, namespace, workerId, "worker", "worker-tidb", 1)

	scheme := buildTestScheme(t)
	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(workerGroup).Build()

	op := NewTiOperator(cli)
	ctx := context.Background()

	// List
	t.Run("ListTiDBWorkers", func(t *testing.T) {
		workers, err := op.ListTiDBWorkers(ctx, clusterId)
		require.NoError(t, err)
		assert.Len(t, workers, 1)
	})

	// Get
	t.Run("GetTiDBWorker", func(t *testing.T) {
		worker, err := op.GetTiDBWorker(ctx, clusterId, workerId)
		require.NoError(t, err)
		require.NotNil(t, worker)
		assert.Equal(t, apiv1.ComponentType_TIDB_WORKER, worker.GetComponentType())
	})

	// Update
	t.Run("UpdateTiDBWorker", func(t *testing.T) {
		options := []UpdateOption{{Option: OpsUpdateNodeCount, Value: uint32(2)}}
		updated, err := op.UpdateTiDBWorker(ctx, clusterId, workerId, options...)
		require.NoError(t, err)
		require.NotNil(t, updated)
		assert.Equal(t, uint32(2), updated.GetNodeQuota().GetNodeCount())
	})
}

// TiKV Group operations
func TestTiOperatorImpl_TiKVGroupOperations(t *testing.T) {
	clusterId := "test-cluster"
	namespace := dataplaneNamespace(clusterId)
	tikvId := testComponentId("tikv")
	tikvGroup := newTiKVGroup(t, namespace, tikvId, 3)

	scheme := buildTestScheme(t)
	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(tikvGroup).Build()

	op := NewTiOperator(cli)
	ctx := context.Background()

	// List
	t.Run("ListTiKVGroups", func(t *testing.T) {
		components, err := op.ListTiKVGroups(ctx, clusterId)
		require.NoError(t, err)
		assert.Len(t, components, 1)
	})

	// Get
	t.Run("GetTiKVGroup", func(t *testing.T) {
		groupId := groupIdWithComponent(tikvId, "tikv")
		component, err := op.GetTiKVGroup(ctx, clusterId, groupId)
		require.NoError(t, err)
		require.NotNil(t, component)
		assert.Equal(t, apiv1.ComponentType_TIKV, component.GetComponentType())
	})

	// Update
	t.Run("UpdateTiKVGroup", func(t *testing.T) {
		groupId := groupIdWithComponent(tikvId, "tikv")
		options := []UpdateOption{{Option: OpsUpdateNodeCount, Value: uint32(5)}}
		updated, err := op.UpdateTiKVGroup(ctx, clusterId, groupId, options...)
		require.NoError(t, err)
		require.NotNil(t, updated)
		assert.Equal(t, uint32(5), updated.GetNodeQuota().GetNodeCount())
	})
}

// PD Group operations
func TestTiOperatorImpl_PDGroupOperations(t *testing.T) {
	clusterId := "test-cluster"
	namespace := dataplaneNamespace(clusterId)
	pdId := testComponentId("pd")
	pdGroup := newPDGroup(t, namespace, pdId, 3)

	scheme := buildTestScheme(t)
	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(pdGroup).Build()

	op := NewTiOperator(cli)
	ctx := context.Background()

	// List
	t.Run("ListPDGroups", func(t *testing.T) {
		components, err := op.ListPDGroups(ctx, clusterId)
		require.NoError(t, err)
		assert.Len(t, components, 1)
	})

	// Get
	t.Run("GetPDGroup", func(t *testing.T) {
		groupId := groupIdWithComponent(pdId, "pd")
		component, err := op.GetPDGroup(ctx, clusterId, groupId)
		require.NoError(t, err)
		require.NotNil(t, component)
		assert.Equal(t, apiv1.ComponentType_PD, component.GetComponentType())
	})

	// Update
	t.Run("UpdatePDGroup", func(t *testing.T) {
		groupId := groupIdWithComponent(pdId, "pd")
		options := []UpdateOption{{Option: OpsUpdateNodeCount, Value: uint32(5)}}
		updated, err := op.UpdatePDGroup(ctx, clusterId, groupId, options...)
		require.NoError(t, err)
		require.NotNil(t, updated)
		assert.Equal(t, uint32(5), updated.GetNodeQuota().GetNodeCount())
	})
}

// TSO Group operations
func TestTiOperatorImpl_TsoGroupOperations(t *testing.T) {
	clusterId := "test-cluster"
	namespace := dataplaneNamespace(clusterId)
	tsoId := testComponentId("tso")
	tsoGroup := newTsoGroup(t, namespace, tsoId, 3)

	scheme := buildTestScheme(t)
	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(tsoGroup).Build()

	op := NewTiOperator(cli)
	ctx := context.Background()

	// List
	t.Run("ListTsoGroups", func(t *testing.T) {
		components, err := op.ListTsoGroups(ctx, clusterId)
		require.NoError(t, err)
		assert.Len(t, components, 1)
	})

	// Get
	t.Run("GetTsoGroup", func(t *testing.T) {
		groupId := groupIdWithComponent(tsoId, "tso")
		component, err := op.GetTsoGroup(ctx, clusterId, groupId)
		require.NoError(t, err)
		require.NotNil(t, component)
		assert.Equal(t, apiv1.ComponentType_PD_TSO, component.GetComponentType())
	})

	// Update
	t.Run("UpdateTsoGroup", func(t *testing.T) {
		groupId := groupIdWithComponent(tsoId, "tso")
		options := []UpdateOption{{Option: OpsUpdateNodeCount, Value: uint32(5)}}
		updated, err := op.UpdateTsoGroup(ctx, clusterId, groupId, options...)
		require.NoError(t, err)
		require.NotNil(t, updated)
		assert.Equal(t, uint32(5), updated.GetNodeQuota().GetNodeCount())
	})
}

// Scheduling Group operations
func TestTiOperatorImpl_SchedulingGroupOperations(t *testing.T) {
	clusterId := "test-cluster"
	namespace := dataplaneNamespace(clusterId)
	schedId := testComponentId("scheduling")
	schedGroup := newSchedulingGroup(t, namespace, schedId, 2)

	scheme := buildTestScheme(t)
	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(schedGroup).Build()

	op := NewTiOperator(cli)
	ctx := context.Background()

	// List
	t.Run("ListSchedulingGroups", func(t *testing.T) {
		components, err := op.ListSchedulingGroups(ctx, clusterId)
		require.NoError(t, err)
		assert.Len(t, components, 1)
	})

	// Get
	t.Run("GetSchedulingGroup", func(t *testing.T) {
		groupId := groupIdWithComponent(schedId, "scheduling")
		component, err := op.GetSchedulingGroup(ctx, clusterId, groupId)
		require.NoError(t, err)
		require.NotNil(t, component)
		assert.Equal(t, apiv1.ComponentType_PD_SCHEDULING, component.GetComponentType())
	})

	// Update
	t.Run("UpdateSchedulingGroup", func(t *testing.T) {
		groupId := groupIdWithComponent(schedId, "scheduling")
		options := []UpdateOption{{Option: OpsUpdateNodeCount, Value: uint32(3)}}
		updated, err := op.UpdateSchedulingGroup(ctx, clusterId, groupId, options...)
		require.NoError(t, err)
		require.NotNil(t, updated)
		assert.Equal(t, uint32(3), updated.GetNodeQuota().GetNodeCount())
	})
}

// TiProxy Group operations
func TestTiOperatorImpl_TiProxyGroupOperations(t *testing.T) {
	clusterId := "test-cluster"
	namespace := dataplaneNamespace(clusterId)
	proxyId := testComponentId("tiproxy")
	proxyGroup := newTiProxyGroup(t, namespace, proxyId, 2)

	scheme := buildTestScheme(t)
	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(proxyGroup).Build()

	op := NewTiOperator(cli)
	ctx := context.Background()

	// List
	t.Run("ListTiProxyGroups", func(t *testing.T) {
		components, err := op.ListTiProxyGroups(ctx, clusterId)
		require.NoError(t, err)
		assert.Len(t, components, 1)
	})

	// Get
	t.Run("GetTiProxyGroup", func(t *testing.T) {
		groupId := groupIdWithComponent(proxyId, "tiproxy")
		component, err := op.GetTiProxyGroup(ctx, clusterId, groupId)
		require.NoError(t, err)
		require.NotNil(t, component)
		assert.Equal(t, apiv1.ComponentType_TIPROXY, component.GetComponentType())
	})

	// Update
	t.Run("UpdateTiProxyGroup", func(t *testing.T) {
		groupId := groupIdWithComponent(proxyId, "tiproxy")
		options := []UpdateOption{{Option: OpsUpdateNodeCount, Value: uint32(3)}}
		updated, err := op.UpdateTiProxyGroup(ctx, clusterId, groupId, options...)
		require.NoError(t, err)
		require.NotNil(t, updated)
		assert.Equal(t, uint32(3), updated.GetNodeQuota().GetNodeCount())
	})
}

// TiFlash operations
func TestTiOperatorImpl_TiFlashOperations(t *testing.T) {
	clusterId := "test-cluster"
	namespace := dataplaneNamespace(clusterId)
	cnId := testComponentId("tiflash-cn")
	wnId := testComponentId("tiflash-wn")

	cnGroup := newTiFlashGroup(t, namespace, cnId, "compute", "compute-tiflash", 2)
	wnGroup := newTiFlashGroup(t, namespace, wnId, "write", "write-tiflash", 3)

	scheme := buildTestScheme(t)
	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cnGroup, wnGroup).Build()

	op := NewTiOperator(cli)
	ctx := context.Background()

	// TiFlash CN tests
	t.Run("TiFlashCN_Operations", func(t *testing.T) {
		// List
		t.Run("ListTiFlashCNs", func(t *testing.T) {
			cns, err := op.ListTiFlashCNs(ctx, clusterId)
			require.NoError(t, err)
			assert.Len(t, cns, 1)
		})

		// Get
		t.Run("GetTiFlashCN", func(t *testing.T) {
			cn, err := op.GetTiFlashCN(ctx, clusterId, cnId)
			require.NoError(t, err)
			require.NotNil(t, cn)
			assert.Equal(t, apiv1.ComponentType_TIFLASH_COMPUTE, cn.GetComponentType())
		})

		// Update
		t.Run("UpdateTiFlashCN", func(t *testing.T) {
			options := []UpdateOption{{Option: OpsUpdateNodeCount, Value: uint32(3)}}
			updated, err := op.UpdateTiFlashCN(ctx, clusterId, cnId, options...)
			require.NoError(t, err)
			require.NotNil(t, updated)
			assert.Equal(t, uint32(3), updated.GetNodeQuota().GetNodeCount())
		})
	})

	// TiFlash WN tests
	t.Run("TiFlashWN_Operations", func(t *testing.T) {
		// List
		t.Run("ListTiFlashWNs", func(t *testing.T) {
			wns, err := op.ListTiFlashWNs(ctx, clusterId)
			require.NoError(t, err)
			assert.Len(t, wns, 1)
		})

		// Get
		t.Run("GetTiFlashWN", func(t *testing.T) {
			wn, err := op.GetTiFlashWN(ctx, clusterId, wnId)
			require.NoError(t, err)
			require.NotNil(t, wn)
			assert.Equal(t, apiv1.ComponentType_TIFLASH_WRITE, wn.GetComponentType())
		})

		// Update
		t.Run("UpdateTiFlashWN", func(t *testing.T) {
			options := []UpdateOption{{Option: OpsUpdateNodeCount, Value: uint32(4)}}
			updated, err := op.UpdateTiFlashWN(ctx, clusterId, wnId, options...)
			require.NoError(t, err)
			require.NotNil(t, updated)
			assert.Equal(t, uint32(4), updated.GetNodeQuota().GetNodeCount())
		})
	})
}

// TiKV Worker operations
func TestTiOperatorImpl_TiKVWorkerOperations(t *testing.T) {
	clusterId := "test-cluster"
	namespace := dataplaneNamespace(clusterId)
	workerId := testComponentId("tikv-worker")

	worker := newTiKVWorkerGroup(t, namespace, workerId, tikvWorkerComponentLabel, 2)

	scheme := buildTestScheme(t)
	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(worker).Build()

	op := NewTiOperator(cli)
	ctx := context.Background()

	// List
	t.Run("ListTiKVWorkers", func(t *testing.T) {
		workers, err := op.ListTiKVWorkers(ctx, clusterId)
		require.NoError(t, err)
		require.Len(t, workers, 1)
		assert.Equal(t, apiv1.ComponentType_TIKV_WORKER, workers[0].GetComponentType())
	})

	// Get
	t.Run("GetTiKVWorker", func(t *testing.T) {
		component, err := op.GetTiKVWorker(ctx, clusterId, workerId)
		require.NoError(t, err)
		require.NotNil(t, component)
		assert.Equal(t, apiv1.ComponentType_TIKV_WORKER, component.GetComponentType())
	})

	// Update
	t.Run("UpdateTiKVWorker", func(t *testing.T) {
		options := []UpdateOption{{Option: OpsUpdateNodeCount, Value: uint32(3)}}
		updated, err := op.UpdateTiKVWorker(ctx, clusterId, workerId, options...)
		require.NoError(t, err)
		require.NotNil(t, updated)
		assert.Equal(t, uint32(3), updated.GetNodeQuota().GetNodeCount())
	})
}

// Coprocessor Worker operations
func TestTiOperatorImpl_CoprocessorWorkerOperations(t *testing.T) {
	clusterId := "test-cluster"
	namespace := dataplaneNamespace(clusterId)
	coprId := testComponentId("copr-worker")

	copr := newTiKVWorkerGroup(t, namespace, coprId, coprocessorWorkerComponentLabel, 2)

	scheme := buildTestScheme(t)
	cli := fake.NewClientBuilder().WithScheme(scheme).WithObjects(copr).Build()

	op := NewTiOperator(cli)
	ctx := context.Background()

	// List
	t.Run("ListCoprocessorWorkers", func(t *testing.T) {
		workers, err := op.ListCoprocessorWorkers(ctx, clusterId)
		require.NoError(t, err)
		require.Len(t, workers, 1)
		assert.Equal(t, apiv1.ComponentType_COPROCESSOR_WORKER, workers[0].GetComponentType())
	})

	// Get
	t.Run("GetCoprocessorWorker", func(t *testing.T) {
		component, err := op.GetCoprocessorWorker(ctx, clusterId, coprId)
		require.NoError(t, err)
		require.NotNil(t, component)
		assert.Equal(t, apiv1.ComponentType_COPROCESSOR_WORKER, component.GetComponentType())
	})

	// Update
	t.Run("UpdateCoprocessorWorker", func(t *testing.T) {
		options := []UpdateOption{{Option: OpsUpdateNodeCount, Value: uint32(3)}}
		updated, err := op.UpdateCoprocessorWorker(ctx, clusterId, coprId, options...)
		require.NoError(t, err)
		require.NotNil(t, updated)
		assert.Equal(t, uint32(3), updated.GetNodeQuota().GetNodeCount())
	})
}
