//nolint:testpackage
package impl

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	"github.com/tidbcloud/dataflow-service/app/repo/entity"
	repomock "github.com/tidbcloud/dataflow-service/app/repo/mock"
	grpcmock "github.com/tidbcloud/dataflow-service/pkg/grpc/grpcli/mock"
	"github.com/tidbcloud/pkgv2/bizerr"
	dedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
)

func TestBackupBillingRetainedBackup(t *testing.T) {
	start := time.Date(2026, 9, 6, 0, 0, 0, 0, time.UTC)
	end := start.Add(24 * time.Hour)
	deletedAt := start.Add(12 * time.Hour)
	const clusterID entity.ID = 123

	for name, tc := range map[string]struct {
		state          dedicatedv1.Cluster_State
		category       MeteringCategory
		backupDeleted  *time.Time
		alreadyMetered bool
		lookupError    bool
		wantTotal      int64
	}{
		"active snapshot":                   {state: dedicatedv1.Cluster_AVAILABLE, category: SnapshotCategory, wantTotal: 2880},
		"deleted cluster retained snapshot": {state: dedicatedv1.Cluster_DELETED, category: SnapshotCategory, wantTotal: 2880},
		"snapshot deleted during metering day": {
			state: dedicatedv1.Cluster_DELETED, category: SnapshotCategory, backupDeleted: &deletedAt, wantTotal: 1440,
		},
		"deleted cluster retained log backup": {state: dedicatedv1.Cluster_DELETED, category: LogCategory, wantTotal: 2880},
		"snapshot already metered":            {state: dedicatedv1.Cluster_DELETED, category: SnapshotCategory, alreadyMetered: true},
		"log backup already metered":          {state: dedicatedv1.Cluster_DELETED, category: LogCategory, alreadyMetered: true},
		"cluster lookup failure":              {category: SnapshotCategory, lookupError: true},
	} {
		t.Run(name, func(t *testing.T) {
			ctx := context.Background()
			records := repomock.NewBillingRecordRepo(t)
			mgmtCli := grpcmock.NewMgmtSvcGrpcClient(t)
			accountCli := grpcmock.NewAccountSvcGrpcClient(t)
			base := baseBackupBillingRecordGeneratorImpl{
				transProvider: &fakeTransProvider{}, billingRecordRepo: records,
				mgmtCli: mgmtCli, accountSvcGrpcClient: accountCli,
			}

			// An older record must not suppress today's metering or cause a historical backfill.
			previousTS := start.Add(-90 * 24 * time.Hour)
			if tc.alreadyMetered {
				previousTS = start
			}
			records.On("Get", mock.Anything, mock.Anything).
				Return(&entity.BillingRecord{MeasuredTS: previousTS}, nil).Once()

			lookupErr := bizerr.New(bizerr.ErrCodeRecordNotFound, "cluster not found")
			if !tc.alreadyMetered {
				cluster := dedicatedClusterWithState(tc.state)
				cluster.ClusterId = clusterID.Uint64()
				cluster.OrganizationId = 456
				cluster.ProjectId = 789
				cluster.ClusterName = "retained-backup-cluster"
				cluster.CloudProvider = "aws"
				cluster.Region = "ap-northeast-1"
				call := mgmtCli.On("GetAnyDedicatedClusterBasicByID", mock.Anything, clusterID.Uint64()).Once()
				if tc.lookupError {
					call.Return(nil, lookupErr)
				} else {
					call.Return(cluster, nil)
					accountCli.On("GetProjectByID", mock.Anything, &account.GetProjectByIDRequest{Id: 789}).
						Return(&account.GetProjectByIDResponse{Project: &account.Project{Name: "project"}}, nil).Once()
					records.On("Save", mock.Anything, mock.MatchedBy(func(record *entity.BillingRecord) bool {
						return record.ClusterID == clusterID && record.TenantID == 456 && record.ProjectID == 789 &&
							record.Category == string(tc.category) && record.SKU == "backup-storage:aws:ap-northeast-1" &&
							record.Total == tc.wantTotal && record.MeasuredTS.Equal(start) && !record.Synced &&
							record.CostID != "" && record.ExtraLabels["cluster"] == cluster.ClusterName
					})).Return(nil).Once()
				}
			}

			var err bizerr.BizError
			if tc.category == LogCategory {
				generator := &logBackupBillingRecordGeneratorImpl{baseBackupBillingRecordGeneratorImpl: base}
				err = generator.generateBillingForOneCluster(ctx, clusterID, []*entity.LogBackupSize{{
					LogBackup: &entity.LogBackup{ClusterID: clusterID},
					TotalSize: 2 * 1024 * 1024 * 1024, StartTS: start, EndTS: end,
				}}, start, end)
			} else {
				generator := &backupBillingRecordGeneratorImpl{baseBackupBillingRecordGeneratorImpl: base}
				retained := &entity.Backup{
					ClusterID: clusterID, BackupSize: 2 * 1024 * 1024 * 1024,
					Status: entity.BackupStatusSuccess, DeletedAt: tc.backupDeleted,
				}
				retained.CreatedAt = start.Add(-180 * 24 * time.Hour)
				err = generator.generateBillingForOneCluster(ctx, clusterID, []*entity.Backup{retained}, start, end)
			}
			if tc.lookupError {
				require.Equal(t, lookupErr, err)
			} else {
				require.Nil(t, err)
			}
			if tc.alreadyMetered || tc.lookupError {
				records.AssertNotCalled(t, "Save", mock.Anything, mock.Anything)
				accountCli.AssertNotCalled(t, "GetProjectByID", mock.Anything, mock.Anything)
			}
		})
	}
}
