package br

import (
	"testing"

	"github.com/stretchr/testify/require"
	clusterngv1beta "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"go.uber.org/mock/gomock"

	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	mockregionalserver "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/adapter/regionalserver/mock_regionalserver"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	mockbrrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/br/mock_br"
	mockclusterrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/cluster/mock_cluster"
	mockregionrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/region/mock_region"
	resourcepoolrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/resourcepool"
	mockresourcepoolrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/resourcepool/mock_resourcepool"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/workflow/common"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
	mockworkflow "github.com/tidbcloud/tidb-management-service/utilities/workflow/mock_workflow"
)

func TestManualRestoreSubmitRejectsDeletedTargetAfterPrepare(t *testing.T) {
	ctrl := gomock.NewController(t)
	clusterRepo := mockclusterrepo.NewMockRepo(ctrl)
	resourcePoolRepo := mockresourcepoolrepo.NewMockRepo(ctrl)
	targetCluster := &entity.Cluster{Cluster: &globalngv1.Cluster{
		ClusterId:      456,
		SharedPoolId:   "123",
		OrganizationId: 789,
	}}
	clusterRepo.EXPECT().Get(gomock.Any(), uint64(456), true).Return(targetCluster, nil)
	gomock.InOrder(
		resourcePoolRepo.EXPECT().LoadLifecycleForUpdate(gomock.Any(), uint64(123), false).Return(&resourcepoolrepo.Lifecycle{
			State: globalngv1.ResourcePool_ACTIVE,
		}, nil),
		clusterRepo.EXPECT().Get(gomock.Any(), uint64(456), false).Return(
			nil,
			bizerr.New(bizerr.ErrCodeRecordNotFound, "cluster not found"),
		),
	)

	adapter := &restoreFromManualAdapter{
		transaction:      newMockTransactioner(),
		clusterRepo:      clusterRepo,
		resourcePoolRepo: resourcePoolRepo,
	}
	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{OrgID: 789, UserID: "test"})
	submit, berr := adapter.PrepareRestore(ctx, &globalngv1.RestoreClusterRequest{
		BackupId:        "100",
		SourceClusterId: "200",
		TargetClusterId: "456",
	})
	require.NoError(t, berr)

	berr = submit(ctx)
	require.ErrorContains(t, berr, "failed to reload target cluster")
}

type manualStopRestoreFixture struct {
	adapter     *restoreFromManualAdapter
	workflow    *mockworkflow.MockWorkflow
	restoreRepo *mockbrrepo.MockRestoreRepo
	clusterRepo *mockclusterrepo.MockRepo
	regionRepo  *mockregionrepo.MockRepo
	regionalCli *mockregionalserver.MockClient
}

func newManualStopRestoreFixture(t *testing.T) *manualStopRestoreFixture {
	t.Helper()

	ctrl := gomock.NewController(t)
	workflow := mockworkflow.NewMockWorkflow(ctrl)
	restoreRepo := mockbrrepo.NewMockRestoreRepo(ctrl)
	clusterRepo := mockclusterrepo.NewMockRepo(ctrl)
	regionRepo := mockregionrepo.NewMockRepo(ctrl)
	regionalCli := mockregionalserver.NewMockClient(ctrl)

	adapter := &restoreFromManualAdapter{
		workflow:    workflow,
		restoreRepo: restoreRepo,
		clusterRepo: clusterRepo,
		regionRepo:  regionRepo,
		regionalCli: regionalCli,
	}
	return &manualStopRestoreFixture{
		adapter:     adapter,
		workflow:    workflow,
		restoreRepo: restoreRepo,
		clusterRepo: clusterRepo,
		regionRepo:  regionRepo,
		regionalCli: regionalCli,
	}
}

func manualStopRestoreEntities() (*entity.Restore, *entity.Cluster, *entity.Region) {
	restore := &entity.Restore{
		Restore: &schema.Restore{
			BackupId:        93934,
			SourceClusterId: 10094942603504306396,
			TargetClusterId: 10248330328218523065,
		},
	}
	targetCluster := &entity.Cluster{
		Cluster: &globalngv1.Cluster{
			ClusterId: 10248330328218523065,
		},
		RegionId: 42,
	}
	targetRegion := &entity.Region{RegionID: 42}
	return restore, targetCluster, targetRegion
}

func TestManualStopRestore_CallsRegionalDeleteManualBackupRestore(t *testing.T) {
	fixture := newManualStopRestoreFixture(t)
	restore, targetCluster, targetRegion := manualStopRestoreEntities()

	req := &globalngv1.StopRestoreClusterRequest{TargetClusterId: "10248330328218523065"}

	fixture.workflow.EXPECT().
		DeleteDagInstancesWithDagId(gomock.Any(), "10248330328218523065", common.RestoreFromManualBackup).
		Return(nil)
	fixture.restoreRepo.EXPECT().
		Get(gomock.Any(), uint64(10248330328218523065)).
		Return(restore, nil)
	fixture.clusterRepo.EXPECT().
		Get(gomock.Any(), uint64(10248330328218523065), true).
		Return(targetCluster, nil)
	fixture.regionRepo.EXPECT().
		Get(gomock.Any(), uint64(42)).
		Return(targetRegion, nil)
	fixture.regionalCli.EXPECT().
		DeleteManualBackupRestore(gomock.Any(), targetRegion, &clusterngv1beta.DeleteManualBackupRestoreRequest{
			SourceClusterId: "10094942603504306396",
			BackupId:        "93934",
			RestoreId:       "10248330328218523065",
		}).
		Return(nil)

	berr := fixture.adapter.StopRestore(t.Context(), req)
	require.Nil(t, berr)
}

func TestManualStopRestore_GlobalDagDeleteFailureSkipsRegionalCall(t *testing.T) {
	fixture := newManualStopRestoreFixture(t)

	req := &globalngv1.StopRestoreClusterRequest{TargetClusterId: "10248330328218523065"}

	// no expectations on restoreRepo/clusterRepo/regionRepo/regionalCli:
	// gomock fails the test if any of them is called
	fixture.workflow.EXPECT().
		DeleteDagInstancesWithDagId(gomock.Any(), "10248330328218523065", common.RestoreFromManualBackup).
		Return(bizerr.New(bizerr.GeneralInternalErrorCode, "db down"))

	berr := fixture.adapter.StopRestore(t.Context(), req)
	require.NotNil(t, berr)
	require.Contains(t, berr.Error(), "failed to delete restore from manual backup dag instances")
}

func TestManualStopRestore_RestoreRecordGetFailureSkipsRegionalCall(t *testing.T) {
	fixture := newManualStopRestoreFixture(t)

	req := &globalngv1.StopRestoreClusterRequest{TargetClusterId: "10248330328218523065"}

	fixture.workflow.EXPECT().
		DeleteDagInstancesWithDagId(gomock.Any(), "10248330328218523065", common.RestoreFromManualBackup).
		Return(nil)
	fixture.restoreRepo.EXPECT().
		Get(gomock.Any(), uint64(10248330328218523065)).
		Return(nil, bizerr.New(bizerr.GeneralInternalErrorCode, "record not found"))

	berr := fixture.adapter.StopRestore(t.Context(), req)
	require.NotNil(t, berr)
	require.Contains(t, berr.Error(), "failed to get restore record")
}

func TestManualStopRestore_RegionalDeleteFailurePropagates(t *testing.T) {
	fixture := newManualStopRestoreFixture(t)
	restore, targetCluster, targetRegion := manualStopRestoreEntities()

	req := &globalngv1.StopRestoreClusterRequest{TargetClusterId: "10248330328218523065"}

	fixture.workflow.EXPECT().
		DeleteDagInstancesWithDagId(gomock.Any(), "10248330328218523065", common.RestoreFromManualBackup).
		Return(nil)
	fixture.restoreRepo.EXPECT().
		Get(gomock.Any(), uint64(10248330328218523065)).
		Return(restore, nil)
	fixture.clusterRepo.EXPECT().
		Get(gomock.Any(), uint64(10248330328218523065), true).
		Return(targetCluster, nil)
	fixture.regionRepo.EXPECT().
		Get(gomock.Any(), uint64(42)).
		Return(targetRegion, nil)
	fixture.regionalCli.EXPECT().
		DeleteManualBackupRestore(gomock.Any(), targetRegion, gomock.Any()).
		Return(bizerr.New(bizerr.GeneralInternalErrorCode, "regional unreachable"))

	berr := fixture.adapter.StopRestore(t.Context(), req)
	require.NotNil(t, berr)
	require.Contains(t, berr.Error(), "failed to delete manual backup restore in cluster service")
}

func TestManualStopRestore_InvalidTargetClusterId(t *testing.T) {
	fixture := newManualStopRestoreFixture(t)

	req := &globalngv1.StopRestoreClusterRequest{TargetClusterId: "not-a-number"}

	fixture.workflow.EXPECT().
		DeleteDagInstancesWithDagId(gomock.Any(), "not-a-number", common.RestoreFromManualBackup).
		Return(nil)

	berr := fixture.adapter.StopRestore(t.Context(), req)
	require.NotNil(t, berr)
	require.Contains(t, berr.Error(), "failed to parse target cluster id")
}
