package service

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/pkgv2/clusterproxy/serverless"
	"github.com/tidbcloud/serverless-service/internal/domain/entity"
	"github.com/tidbcloud/serverless-service/internal/domain/facade/mock_facade"
	"go.uber.org/mock/gomock"
)

func TestVirtualizedNextSlotBackfillsFromCutover(t *testing.T) {
	due := time.Date(2026, time.July, 13, 8, 30, 0, 0, time.UTC)
	cutover := due.Add(-40 * time.Minute)
	require.Equal(t, due.Add(-30*time.Minute), virtualizedNextSlot(nil, due, &cutover))
}

func TestVirtualizedNextSlotContinuesAfterLatestSlot(t *testing.T) {
	due := time.Date(2026, time.July, 13, 8, 30, 0, 0, time.UTC)
	latest := &entity.PhysicalBackupSlot{SlotTime: due.Add(-30 * time.Minute)}
	require.Equal(t, due, virtualizedNextSlot(latest, due, nil))
}

func TestVirtualizedSlotAtOrAfterRoundsCutoverUp(t *testing.T) {
	cutover := time.Date(2026, time.July, 13, 8, 0, 1, 0, time.UTC)
	require.Equal(t, time.Date(2026, time.July, 13, 8, 30, 0, 0, time.UTC), virtualizedSlotAtOrAfter(cutover))
}

func TestVirtualizedNextSlotsCapsContiguousBatch(t *testing.T) {
	due := time.Date(2026, time.July, 13, 12, 0, 0, 0, time.UTC)
	latest := &entity.PhysicalBackupSlot{SlotTime: due.Add(-3 * time.Hour)}

	slots := virtualizedNextSlots(latest, due, nil, 4)

	require.Equal(t, []time.Time{
		due.Add(-150 * time.Minute),
		due.Add(-120 * time.Minute),
		due.Add(-90 * time.Minute),
		due.Add(-60 * time.Minute),
	}, slots)
}

func TestBuildPhysicalBackupSlotsUsesEarliestBackupAndStopsAtGap(t *testing.T) {
	firstSlot := time.Date(2026, time.July, 13, 10, 0, 0, 0, time.UTC)
	slots := []time.Time{firstSlot, firstSlot.Add(30 * time.Minute), firstSlot.Add(time.Hour)}
	backups := []serverless.BackupItem{
		{ID: 2, Name: "second", Time: serverless.JSONTime(firstSlot.Add(50 * time.Minute))},
		{ID: 1, Name: "first", Time: serverless.JSONTime(firstSlot.Add(20 * time.Minute))},
	}

	resolved := buildPhysicalBackupSlots("aws", "region", "kubernetes", slots, backups)

	require.Len(t, resolved, 2)
	require.Equal(t, uint64(1), resolved[0].BackupID)
	require.Equal(t, uint64(2), resolved[1].BackupID)
	require.Equal(t, []time.Time{resolved[0].SlotTime, resolved[1].SlotTime}, slots[:2])
}

func TestListPhysicalBackupsForSlotBatchPaginatesUntilLastSlotIsCovered(t *testing.T) {
	ctrl := gomock.NewController(t)
	proxy := mock_facade.NewMockServerlessClusterProxyFacade(ctrl)
	firstSlot := time.Date(2026, time.July, 13, 10, 0, 0, 0, time.UTC)
	lastSlot := firstSlot.Add(time.Hour)
	pageBoundary := firstSlot.Add(40 * time.Minute)

	firstPage := proxy.EXPECT().ListBackups(gomock.Any(), serverless.ListBackupRequest{
		LastBackupTime: serverless.JSONTime(firstSlot.Add(-time.Second)),
		MaxCount:       virtualizedBackupListPageMaxCount,
	}).Return(&serverless.ListBackupResponse{
		Items: []serverless.BackupItem{
			{ID: 2, Time: serverless.JSONTime(pageBoundary)},
			{ID: 1, Time: serverless.JSONTime(firstSlot.Add(10 * time.Minute))},
		},
		HasMore: true,
	}, nil)
	proxy.EXPECT().ListBackups(gomock.Any(), serverless.ListBackupRequest{
		LastBackupTime: serverless.JSONTime(pageBoundary),
		MaxCount:       virtualizedBackupListPageMaxCount,
	}).After(firstPage).Return(&serverless.ListBackupResponse{
		Items: []serverless.BackupItem{{ID: 3, Time: serverless.JSONTime(lastSlot.Add(5 * time.Minute))}},
	}, nil)

	backups, err := listPhysicalBackupsForSlotBatch(context.Background(), proxy, firstSlot, lastSlot)

	require.NoError(t, err)
	require.Len(t, backups, 3)
}
