package service

import (
	"context"
	"testing"
	"time"

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

func TestResolveBackupResolvesPersistedBackupWithinCluster(t *testing.T) {
	ctrl := gomock.NewController(t)
	backupRepo := mock_repo.NewMockUserBackupRepo(ctrl)
	backup := &entity.UserBackup{ID: "bak-persisted", ClusterID: testClusterID}
	backupRepo.EXPECT().
		GetByClusterIdAndBackupId(gomock.Any(), testClusterID, backup.ID).
		Return(backup, nil)
	svc := &backupRestoreService{backupRepo: backupRepo}

	resolved, err := svc.resolveBackup(
		context.Background(),
		testClusterID,
		backup.ID,
		backupPhysical,
	)

	require.NoError(t, err)
	require.True(t, resolved.isPersisted())
	require.False(t, resolved.hasVirtualRestorePoint())
	require.Same(t, backup, resolved.Backup)
	require.Nil(t, resolved.History)
}

func TestResolveBackupResolvesPersistedBackupWithoutClusterScope(t *testing.T) {
	ctrl := gomock.NewController(t)
	backupRepo := mock_repo.NewMockUserBackupRepo(ctrl)
	backup := &entity.UserBackup{ID: "bak-persisted", ClusterID: testClusterID}
	backupRepo.EXPECT().GetByBackupId(gomock.Any(), backup.ID).Return(backup, nil)
	svc := &backupRestoreService{backupRepo: backupRepo}

	resolved, err := svc.resolveBackup(context.Background(), "", backup.ID, backupPhysical)

	require.NoError(t, err)
	require.True(t, resolved.isPersisted())
	require.False(t, resolved.hasVirtualRestorePoint())
	require.Same(t, backup, resolved.Backup)
}

func TestResolveBackupRejectsPersistedRestorePointWithTombstone(t *testing.T) {
	ctrl := gomock.NewController(t)
	backupRepo := mock_repo.NewMockUserBackupRepo(ctrl)
	tombstoneRepo := mock_repo.NewMockBackupTombstoneRepo(ctrl)
	scheduleTime := todayMidnightUTC()
	backup := &entity.UserBackup{ID: "legacy-late-write", ClusterID: testClusterID, ScheduleTime: &scheduleTime}
	backupRepo.EXPECT().GetByClusterIdAndBackupId(gomock.Any(), testClusterID, backup.ID).Return(backup, nil)
	tombstoneRepo.EXPECT().Exists(gomock.Any(), testClusterID, scheduleTime).Return(true, nil)
	svc := &backupRestoreService{backupRepo: backupRepo, backupTombstoneRepo: tombstoneRepo}

	_, err := svc.resolveBackup(context.Background(), testClusterID, backup.ID, backupPhysical)

	require.Error(t, err)
	require.Equal(t, bizerr.ErrCodeRecordNotFound, err.ErrCode())
}

func TestResolveBackupRejectsMalformedVirtualID(t *testing.T) {
	svc := &backupRestoreService{}

	_, err := svc.resolveBackup(context.Background(), testClusterID, constant.BackupIDPrefix+"v1-!!!", backupPhysical)

	require.Error(t, err)
	require.Equal(t, bizerr.ErrCodeParams, err.ErrCode())
}

func TestResolveBackupResolvesVirtualRestorePoint(t *testing.T) {
	ctrl := gomock.NewController(t)
	svc, _, historyRepo, tombstoneRepo := newBackupRestoreServiceForVirtualTest(ctrl)
	withoutCurrentSchedule(svc)
	scheduleTime := todayMidnightUTC()
	backupID := generateVirtualBackupID(testClusterID, scheduleTime)

	tombstoneRepo.EXPECT().Exists(gomock.Any(), testClusterID, scheduleTime).Return(false, nil)
	historyRepo.EXPECT().
		List(gomock.Any(), gomock.Any()).
		Return([]*entity.BackupScheduleHistory{historyForSchedule(scheduleTime)}, nil)

	resolved, err := svc.resolveBackup(context.Background(), testClusterID, backupID, backupRestorePoint)

	require.NoError(t, err)
	require.False(t, resolved.isPersisted())
	require.True(t, resolved.hasVirtualRestorePoint())
	require.NotNil(t, resolved.Virtual)
	require.Equal(t, backupID, resolved.Virtual.ID)
	require.Nil(t, resolved.Backup)
	require.NotNil(t, resolved.History)
}

func TestResolveBackupResolvesVirtualPhysicalBackup(t *testing.T) {
	ctrl := gomock.NewController(t)
	svc, _, historyRepo, tombstoneRepo := newBackupRestoreServiceForVirtualTest(ctrl)
	withoutCurrentSchedule(svc)
	proxyProvider := mock_facade.NewMockClusterProxyProviderFacade(ctrl)
	proxy := mock_facade.NewMockServerlessClusterProxyFacade(ctrl)
	svc.clusterProxyProvider = proxyProvider
	scheduleTime := todayMidnightUTC()
	backupID := generateVirtualBackupID(testClusterID, scheduleTime)
	actualBackupTime := scheduleTime.Add(30 * time.Second)

	withTestClusterProxyConfig(t)
	tombstoneRepo.EXPECT().Exists(gomock.Any(), testClusterID, scheduleTime).Return(false, nil)
	historyRepo.EXPECT().
		List(gomock.Any(), gomock.Any()).
		Return([]*entity.BackupScheduleHistory{historyForSchedule(scheduleTime)}, nil)
	proxyProvider.EXPECT().GetServerlessProxy(gomock.Any(), testProxyURL).Return(proxy, nil)
	proxy.EXPECT().
		ListBackups(gomock.Any(), gomock.Any()).
		Return(&pkgv2.ListBackupResponse{Items: []pkgv2.BackupItem{{
			ID:   888,
			Name: "real-backup",
			Time: pkgv2.JSONTime(actualBackupTime),
		}}}, nil)

	resolved, err := svc.resolveBackup(context.Background(), testClusterID, backupID, backupPhysical)

	require.NoError(t, err)
	require.False(t, resolved.isPersisted())
	require.True(t, resolved.hasVirtualRestorePoint())
	require.NotNil(t, resolved.Backup)
	require.NotNil(t, resolved.Virtual)
	require.Equal(t, uint64(888), resolved.Backup.BackupID)
}

func TestResolveBackupFailsWhenPhysicalBackupNotReady(t *testing.T) {
	ctrl := gomock.NewController(t)
	svc, _, historyRepo, tombstoneRepo := newBackupRestoreServiceForVirtualTest(ctrl)
	withoutCurrentSchedule(svc)
	proxyProvider := mock_facade.NewMockClusterProxyProviderFacade(ctrl)
	proxy := mock_facade.NewMockServerlessClusterProxyFacade(ctrl)
	svc.clusterProxyProvider = proxyProvider
	scheduleTime := todayMidnightUTC()
	backupID := generateVirtualBackupID(testClusterID, scheduleTime)

	withTestClusterProxyConfig(t)
	tombstoneRepo.EXPECT().Exists(gomock.Any(), testClusterID, scheduleTime).Return(false, nil)
	historyRepo.EXPECT().
		List(gomock.Any(), gomock.Any()).
		Return([]*entity.BackupScheduleHistory{historyForSchedule(scheduleTime)}, nil)
	proxyProvider.EXPECT().GetServerlessProxy(gomock.Any(), testProxyURL).Return(proxy, nil)
	proxy.EXPECT().ListBackups(gomock.Any(), gomock.Any()).Return(&pkgv2.ListBackupResponse{}, nil)

	_, err := svc.resolveBackup(context.Background(), testClusterID, backupID, backupPhysical)

	require.Error(t, err)
	require.Equal(t, bizerr.ErrCodeRecordNotFound, err.ErrCode())
	require.Contains(t, err.Error(), virtualBackupNotReadyMessage)
}
