package server

import (
	"context"
	"fmt"
	"testing"

	meteringsdkcommon "github.com/pingcap/metering_sdk/common"
	meteringsdkwriter "github.com/pingcap/metering_sdk/writer"
	"github.com/stretchr/testify/mock"
	"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"
	repomocks "github.com/tidbcloud/cluster-service-ng/pkg/storage/repo/mocks"
)

type captureMeteringWriter struct {
	data any
}

func (w *captureMeteringWriter) Write(_ context.Context, data any) error {
	w.data = data
	return nil
}

type conflictThenCaptureMeteringWriter struct {
	conflicts int
	data      any
	attempts  int
	modifyTSs []int64
}

func (w *conflictThenCaptureMeteringWriter) Write(_ context.Context, data any) error {
	w.attempts++
	if meta, ok := data.(*meteringsdkcommon.MetaData); ok {
		w.modifyTSs = append(w.modifyTSs, meta.ModifyTS)
		copied := *meta
		copied.Metadata = map[string]any{}
		for k, v := range meta.Metadata {
			copied.Metadata[k] = v
		}
		w.data = &copied
	}
	if w.attempts <= w.conflicts {
		return fmt.Errorf("%w: metering/meta/logic/backup/test.json.gz", meteringsdkwriter.ErrFileExists)
	}
	return nil
}

func TestReportAutoBackupMeteringWritesMeteringDataWithDataWriter(t *testing.T) {
	ctx := context.Background()
	writer := &captureMeteringWriter{}
	s := &serverImpl{meteringDataWriter: writer}

	err := s.ReportAutoBackupMetering(ctx, &apiv1.ReportAutoBackupMeteringRequest{
		ClusterId:    "cluster-1",
		SharedPoolId: "pool-1",
		Timestamp:    123,
		Backups: []*apiv1.AutoBackupMeteringBackup{
			{BackupId: "backup-1", BackupSizeBytes: 1},
			{BackupId: "backup-2", BackupSizeBytes: 1073741824},
		},
	})
	require.NoError(t, err)

	payload, ok := writer.data.(*meteringsdkcommon.MeteringData)
	require.True(t, ok)
	require.Equal(t, int64(123), payload.Timestamp)
	require.Equal(t, flexBackupMeteringCategory, payload.Category)
	require.Equal(t, "pool-1", payload.SharedPoolID)
	require.Len(t, payload.Data, 2)
	require.Equal(t, "cluster-1", payload.Data[0]["cluster_id"])
	require.Equal(t, "backup-1", payload.Data[0]["backup_id"])
	require.Equal(t, &meteringsdkcommon.MeteringValue{Value: uint64(1), Unit: "GB"}, payload.Data[0]["backup_size_gb"])
}

func TestUpdateAutoBackupMeteringAdvancesModifyTSOnFileExists(t *testing.T) {
	ctx := context.Background()
	writer := &conflictThenCaptureMeteringWriter{conflicts: 2}
	clusterRepo := repomocks.NewTiDBClusterRepo(t)
	clusterRepo.On("Load", mock.Anything, "cluster-1", false).
		Return(&entity.TiDBCluster{SharedPoolId: "pool-1"}, nil).
		Once()
	s := &serverImpl{
		meteringWriter:  writer,
		tidbClusterRepo: clusterRepo,
		sharedPoolRepo:  availableSharedPoolRepoForMutationTest(),
	}

	err := s.UpdateAutoBackupMetering(ctx, &apiv1.UpdateAutoBackupMeteringRequest{
		ClusterId: "cluster-1",
		SkuName:   "backup-storage-v2",
		SkuValue:  "T1",
		Mode:      apiv1.AutoBackupMode_FLEXIBLE_RETENTION,
	})
	require.NoError(t, err)
	require.Equal(t, 3, writer.attempts)
	require.Len(t, writer.modifyTSs, 3)
	require.Equal(t, writer.modifyTSs[0]+1, writer.modifyTSs[1])
	require.Equal(t, writer.modifyTSs[1]+1, writer.modifyTSs[2])

	meta, ok := writer.data.(*meteringsdkcommon.MetaData)
	require.True(t, ok)
	require.Equal(t, "cluster-1", meta.ClusterID)
	require.Equal(t, "backup", meta.Category)
	require.Equal(t, flexBackupMeteringSKUValue, meta.Metadata["backup-storage-v2"])
}
