package app

import (
	"context"
	"testing"

	"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"
	servermocks "github.com/tidbcloud/cluster-service-ng/domain/server/mocks"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	"google.golang.org/protobuf/types/known/wrapperspb"
)

func TestAutoscalePolicyBundleHandlers(t *testing.T) {
	ctx := context.Background()
	svc := servermocks.NewService(t)
	app := &appImpl{ngService: svc}

	t.Run("CreateOrReplaceAutoscalePolicy", func(t *testing.T) {
		specs := []*entity.AutoscalePolicySpec{
			{
				TargetType: apiv1.AutoscaleTargetType_TARGET_TIKV,
				Direction:  apiv1.AutoscalePolicy_SCALE_OUT,
				Capability: apiv1.AutoscalePolicy_NODE_COUNT,
				Trigger:    apiv1.AutoscalePolicy_CPU,
				State:      apiv1.AutoscalePolicy_ENABLED,
			},
		}
		want := &entity.AutoscalePolicyResponse{
			Targets: []*entity.AutoscalePolicyApplyTarget{
				{Scope: apiv1.AutoscalePolicyScope_SCOPE_TENANT, SubjectId: "tenant-1", Status: apiv1.AutoscalePolicyApplyStatus_APPLY_STATUS_SYNCED},
			},
		}
		svc.On(
			"CreateOrReplaceAutoscalePolicy",
			ctx,
			apiv1.AutoscalePolicyScope_SCOPE_TENANT,
			"tenant-1",
			specs,
			wrapperspb.Bool(false),
			"req-1",
		).Return(want, nil).Once()

		resp, err := app.CreateOrReplaceAutoscalePolicy(ctx, &apiv1.CreateOrReplaceAutoscalePolicyRequest{
			Scope:                 apiv1.AutoscalePolicyScope_SCOPE_TENANT,
			SubjectId:             "tenant-1",
			Specs:                 specs,
			ApplyToAllSharedPools: wrapperspb.Bool(false),
			RequestId:             "req-1",
		})
		require.NoError(t, err)
		require.Equal(t, want, resp)
	})

	t.Run("PatchAutoscalePolicy", func(t *testing.T) {
		specs := []*entity.AutoscalePolicySpec{
			{
				TargetType: apiv1.AutoscaleTargetType_TARGET_TIKV,
				Direction:  apiv1.AutoscalePolicy_SCALE_OUT,
				Capability: apiv1.AutoscalePolicy_DISK_SIZE,
				Trigger:    apiv1.AutoscalePolicy_DISK,
				State:      apiv1.AutoscalePolicy_DISABLED,
			},
		}
		want := &entity.AutoscalePolicyResponse{
			Targets: []*entity.AutoscalePolicyApplyTarget{
				{Scope: apiv1.AutoscalePolicyScope_SCOPE_SHARED_POOL, SubjectId: "sp-1", Status: apiv1.AutoscalePolicyApplyStatus_APPLY_STATUS_SYNCED},
			},
		}
		svc.On(
			"PatchAutoscalePolicy",
			ctx,
			apiv1.AutoscalePolicyScope_SCOPE_SHARED_POOL,
			"sp-1",
			specs,
			(*wrapperspb.BoolValue)(nil),
			"req-patch",
		).Return(want, nil).Once()

		resp, err := app.PatchAutoscalePolicy(ctx, &apiv1.PatchAutoscalePolicyRequest{
			Scope:     apiv1.AutoscalePolicyScope_SCOPE_SHARED_POOL,
			SubjectId: "sp-1",
			Specs:     specs,
			RequestId: "req-patch",
		})
		require.NoError(t, err)
		require.Equal(t, want, resp)
	})

	t.Run("GetAutoscalePolicy", func(t *testing.T) {
		want := &entity.AutoscalePolicySpecBundle{
			Scope:     apiv1.AutoscalePolicyScope_SCOPE_TENANT,
			SubjectId: "tenant-1",
			Specs: []*entity.AutoscalePolicySpec{
				{
					TargetType: apiv1.AutoscaleTargetType_TARGET_TIKV,
					Direction:  apiv1.AutoscalePolicy_SCALE_OUT,
					Capability: apiv1.AutoscalePolicy_NODE_COUNT,
					Trigger:    apiv1.AutoscalePolicy_CPU,
					State:      apiv1.AutoscalePolicy_ENABLED,
				},
			},
			BundleHash: "abc123",
		}
		svc.On("GetAutoscalePolicy", ctx, apiv1.AutoscalePolicyScope_SCOPE_TENANT, "tenant-1").Return(want, nil).Once()

		resp, err := app.GetAutoscalePolicy(ctx, &apiv1.GetAutoscalePolicyRequest{
			Scope:     apiv1.AutoscalePolicyScope_SCOPE_TENANT,
			SubjectId: "tenant-1",
		})
		require.NoError(t, err)
		require.Equal(t, want, resp.GetBundle())
	})

	t.Run("GetEffectiveAutoscalePolicy", func(t *testing.T) {
		want := &entity.AutoscalePolicySpecBundle{
			Scope:     apiv1.AutoscalePolicyScope_SCOPE_SHARED_POOL,
			SubjectId: "sp-1",
			Specs: []*entity.AutoscalePolicySpec{
				{
					TargetType: apiv1.AutoscaleTargetType_TARGET_TIKV,
					Direction:  apiv1.AutoscalePolicy_SCALE_OUT,
					Capability: apiv1.AutoscalePolicy_NODE_COUNT,
					Trigger:    apiv1.AutoscalePolicy_CPU,
					State:      apiv1.AutoscalePolicy_ENABLED,
				},
			},
			BundleHash: "def456",
		}
		svc.On("GetEffectiveAutoscalePolicy", ctx, apiv1.AutoscalePolicyScope_SCOPE_SHARED_POOL, "sp-1").Return(want, nil).Once()

		resp, err := app.GetEffectiveAutoscalePolicy(ctx, &apiv1.GetEffectiveAutoscalePolicyRequest{
			Scope:     apiv1.AutoscalePolicyScope_SCOPE_SHARED_POOL,
			SubjectId: "sp-1",
		})
		require.NoError(t, err)
		require.Equal(t, want, resp.GetBundle())
	})

	t.Run("ListAutoscalePolicy", func(t *testing.T) {
		want := []*entity.AutoscalePolicySpecBundle{
			{Scope: apiv1.AutoscalePolicyScope_SCOPE_TENANT, SubjectId: "tenant-1"},
		}
		next := "page2"
		filter := repo.AutoscalePolicyBundleFilter{
			Scope:           apiv1.AutoscalePolicyScope_SCOPE_TENANT,
			SubjectIDPrefix: "tenant-",
		}
		svc.On("ListAutoscalePolicy", ctx, filter, uint32(10), "").Return(want, next, nil).Once()

		resp, err := app.ListAutoscalePolicy(ctx, &apiv1.ListAutoscalePolicyRequest{
			Scope:           apiv1.AutoscalePolicyScope_SCOPE_TENANT,
			SubjectIdPrefix: "tenant-",
			PageSize:        10,
		})
		require.NoError(t, err)
		require.Equal(t, want, resp.GetBundles())
		require.Equal(t, next, resp.GetNextPageToken())
	})

	t.Run("PreviewAutoscalePolicy", func(t *testing.T) {
		specs := []*entity.AutoscalePolicySpec{
			{
				TargetType: apiv1.AutoscaleTargetType_TARGET_TIKV,
				Direction:  apiv1.AutoscalePolicy_SCALE_OUT,
				Capability: apiv1.AutoscalePolicy_NODE_COUNT,
				Trigger:    apiv1.AutoscalePolicy_CPU,
				State:      apiv1.AutoscalePolicy_ENABLED,
			},
		}
		want := []*entity.AutoscalePolicyPreviewTarget{
			{Scope: apiv1.AutoscalePolicyScope_SCOPE_TENANT, SubjectId: "tenant-1"},
		}
		svc.On(
			"PreviewAutoscalePolicy",
			ctx,
			apiv1.AutoscalePolicyScope_SCOPE_TENANT,
			"tenant-1",
			specs,
			apiv1.PreviewOperationType_PREVIEW_OP_POST,
			wrapperspb.Bool(false),
		).Return(want, nil).Once()

		resp, err := app.PreviewAutoscalePolicy(ctx, &apiv1.PreviewAutoscalePolicyRequest{
			Scope:                 apiv1.AutoscalePolicyScope_SCOPE_TENANT,
			SubjectId:             "tenant-1",
			Specs:                 specs,
			Operation:             apiv1.PreviewOperationType_PREVIEW_OP_POST,
			ApplyToAllSharedPools: wrapperspb.Bool(false),
		})
		require.NoError(t, err)
		require.Equal(t, want, resp.GetTargets())
	})

	t.Run("SyncAutoscalePolicy", func(t *testing.T) {
		keys := []*entity.AutoscalePolicySpecKey{
			{TargetType: apiv1.AutoscaleTargetType_TARGET_TIKV, Direction: apiv1.AutoscalePolicy_SCALE_OUT, Capability: apiv1.AutoscalePolicy_NODE_COUNT, Trigger: apiv1.AutoscalePolicy_CPU},
		}
		want := &entity.AutoscalePolicyResponse{
			Targets: []*entity.AutoscalePolicyApplyTarget{
				{Scope: apiv1.AutoscalePolicyScope_SCOPE_SHARED_POOL, SubjectId: "sp-1", Status: apiv1.AutoscalePolicyApplyStatus_APPLY_STATUS_SYNCED},
			},
		}
		svc.On(
			"SyncAutoscalePolicy",
			ctx,
			apiv1.AutoscalePolicyScope_SCOPE_TENANT,
			"tenant-1",
			apiv1.SyncAutoscalePolicyMode_SYNC_MODE_REPLACE_ALL,
			keys,
			(*wrapperspb.BoolValue)(nil),
			"req-sync",
		).Return(want, nil).Once()

		resp, err := app.SyncAutoscalePolicy(ctx, &apiv1.SyncAutoscalePolicyRequest{
			Scope:     apiv1.AutoscalePolicyScope_SCOPE_TENANT,
			SubjectId: "tenant-1",
			Mode:      apiv1.SyncAutoscalePolicyMode_SYNC_MODE_REPLACE_ALL,
			Keys:      keys,
			RequestId: "req-sync",
		})
		require.NoError(t, err)
		require.Equal(t, want, resp)
	})
}
