package encryptionkey

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/utilities/idgenerator"
)

func TestClusterEncryptionBinding(t *testing.T) {
	ctx := t.Context()
	ts := newTestService(t)
	sfid := idgenerator.NewSonyflakeID(1, "")

	t.Run("UpsertAndLoad", func(t *testing.T) {
		cleanTestData(ctx, t)

		clusterId := sfid.MustNew()
		keyId := sfid.MustNew()
		binding := &entity.ClusterEncryptionBinding{
			ClusterId:       clusterId,
			TenantId:        testOrgID,
			EncryptionKeyId: keyId,
			State:           entity.ClusterEncryptionBindingStateModifying,
			Message:         "test upsert",
			DagInstanceId:   "dag-123",
		}
		err := ts.bindingRepo.Upsert(ctx, binding)
		require.Nil(t, err)

		loaded, loadErr := ts.bindingRepo.LoadByClusterId(ctx, clusterId)
		require.Nil(t, loadErr)
		assert.Equal(t, clusterId, loaded.ClusterId)
		assert.Equal(t, uint64(testOrgID), loaded.TenantId)
		assert.Equal(t, keyId, loaded.EncryptionKeyId)
		assert.Equal(t, entity.ClusterEncryptionBindingStateModifying, loaded.State)
		assert.Equal(t, "test upsert", loaded.Message)
		assert.Equal(t, "dag-123", loaded.DagInstanceId)
	})

	t.Run("UpsertOverwrite", func(t *testing.T) {
		cleanTestData(ctx, t)

		clusterId := sfid.MustNew()
		keyId1 := sfid.MustNew()
		keyId2 := sfid.MustNew()

		// First upsert
		err := ts.bindingRepo.Upsert(ctx, &entity.ClusterEncryptionBinding{
			ClusterId:       clusterId,
			TenantId:        testOrgID,
			EncryptionKeyId: keyId1,
			State:           entity.ClusterEncryptionBindingStateModifying,
		})
		require.Nil(t, err)

		// Second upsert with same clusterId should overwrite
		err = ts.bindingRepo.Upsert(ctx, &entity.ClusterEncryptionBinding{
			ClusterId:       clusterId,
			TenantId:        testOrgID,
			EncryptionKeyId: keyId2,
			State:           entity.ClusterEncryptionBindingStateModifying,
			DagInstanceId:   "dag-456",
		})
		require.Nil(t, err)

		loaded, loadErr := ts.bindingRepo.LoadByClusterId(ctx, clusterId)
		require.Nil(t, loadErr)
		assert.Equal(t, keyId2, loaded.EncryptionKeyId)
		assert.Equal(t, "dag-456", loaded.DagInstanceId)
	})

	t.Run("UpdateState", func(t *testing.T) {
		cleanTestData(ctx, t)

		clusterId := sfid.MustNew()
		err := ts.bindingRepo.Upsert(ctx, &entity.ClusterEncryptionBinding{
			ClusterId:       clusterId,
			TenantId:        testOrgID,
			EncryptionKeyId: sfid.MustNew(),
			State:           entity.ClusterEncryptionBindingStateModifying,
		})
		require.Nil(t, err)

		err = ts.bindingRepo.UpdateState(ctx, clusterId, entity.ClusterEncryptionBindingStateEnabled, "encryption enabled")
		require.Nil(t, err)

		loaded, _ := ts.bindingRepo.LoadByClusterId(ctx, clusterId)
		assert.Equal(t, entity.ClusterEncryptionBindingStateEnabled, loaded.State)
		assert.Equal(t, "encryption enabled", loaded.Message)
	})

	t.Run("IsModifying", func(t *testing.T) {
		cleanTestData(ctx, t)

		clusterId := sfid.MustNew()

		// No binding → not modifying
		modifying, err := ts.bindingRepo.IsModifying(ctx, clusterId)
		require.Nil(t, err)
		assert.False(t, modifying)

		// Modifying binding → true
		err = ts.bindingRepo.Upsert(ctx, &entity.ClusterEncryptionBinding{
			ClusterId:       clusterId,
			TenantId:        testOrgID,
			EncryptionKeyId: sfid.MustNew(),
			State:           entity.ClusterEncryptionBindingStateModifying,
		})
		require.Nil(t, err)

		modifying, err = ts.bindingRepo.IsModifying(ctx, clusterId)
		require.Nil(t, err)
		assert.True(t, modifying)

		// Enabled binding → false
		err = ts.bindingRepo.UpdateState(ctx, clusterId, entity.ClusterEncryptionBindingStateEnabled, "done")
		require.Nil(t, err)

		modifying, err = ts.bindingRepo.IsModifying(ctx, clusterId)
		require.Nil(t, err)
		assert.False(t, modifying)
	})

	t.Run("Delete", func(t *testing.T) {
		cleanTestData(ctx, t)

		clusterId := sfid.MustNew()
		err := ts.bindingRepo.Upsert(ctx, &entity.ClusterEncryptionBinding{
			ClusterId:       clusterId,
			TenantId:        testOrgID,
			EncryptionKeyId: sfid.MustNew(),
			State:           entity.ClusterEncryptionBindingStateEnabled,
		})
		require.Nil(t, err)

		err = ts.bindingRepo.Delete(ctx, clusterId)
		require.Nil(t, err)

		_, loadErr := ts.bindingRepo.LoadByClusterId(ctx, clusterId)
		assert.NotNil(t, loadErr)
	})

	t.Run("GetEncryptionBindingViaService", func(t *testing.T) {
		cleanTestData(ctx, t)

		clusterId := sfid.MustNew()
		keyId := sfid.MustNew()

		err := ts.bindingRepo.Upsert(ctx, &entity.ClusterEncryptionBinding{
			ClusterId:       clusterId,
			TenantId:        testOrgID,
			EncryptionKeyId: keyId,
			State:           entity.ClusterEncryptionBindingStateEnabled,
			Message:         "active binding",
		})
		require.Nil(t, err)

		result, bizErr := ts.svc.GetEncryptionBinding(ctx, clusterId)
		require.Nil(t, bizErr)
		assert.Equal(t, clusterId, result.ClusterId)
		assert.Equal(t, entity.ClusterEncryptionBindingStateEnabled, result.State)

		// Non-existent binding
		_, bizErr = ts.svc.GetEncryptionBinding(ctx, 99999999)
		assert.NotNil(t, bizErr)
	})
}
