package branch

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/pkgv2/bizerr"
	"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"
	"github.com/tidbcloud/serverless-service/internal/domain/repo/mock_repo"
	"go.uber.org/mock/gomock"
)

func TestBranchReconcilerPollingStages(t *testing.T) {
	now := time.Date(2026, 9, 4, 12, 2, 0, 0, time.UTC)
	cutoff := now.Add(-2 * time.Minute)
	statuses := []constant.BranchStatus{
		constant.BranchStatusPending,
		constant.BranchStatusCreating,
		constant.BranchStatusRestoring,
	}

	tests := []struct {
		name   string
		filter repo.BranchFilter
		run    func(*BranchReconciler, context.Context) error
	}{
		{
			name: "fast stage selects branches younger than two minutes",
			filter: repo.BranchFilter{
				Statuses:     statuses,
				CreatedAfter: &cutoff,
			},
			run: func(r *BranchReconciler, ctx context.Context) error { return r.RunFast(ctx) },
		},
		{
			name: "regular stage includes the two minute boundary",
			filter: repo.BranchFilter{
				Statuses:          statuses,
				CreatedAtOrBefore: &cutoff,
			},
			run: func(r *BranchReconciler, ctx context.Context) error { return r.Run(ctx) },
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			ctrl := gomock.NewController(t)
			branchRepo := mock_repo.NewMockBranchRepo(ctrl)
			branchRepo.EXPECT().ListBranchesByFilter(gomock.Any(), tc.filter).Return(nil, nil)
			r := NewBranchReconciler(nil, branchRepo, nil, nil, nil, nil, nil)
			r.now = func() time.Time { return now }

			require.NoError(t, tc.run(r, context.Background()))
		})
	}
}

func TestBranchReconcilerReturnsRepositoryError(t *testing.T) {
	ctrl := gomock.NewController(t)
	branchRepo := mock_repo.NewMockBranchRepo(ctrl)
	queryErr := bizerr.New(bizerr.ErrCodeDB, "query failed")
	branchRepo.EXPECT().ListBranchesByFilter(gomock.Any(), gomock.Any()).Return(nil, queryErr)
	r := NewBranchReconciler(nil, branchRepo, nil, nil, nil, nil, nil)
	r.now = func() time.Time { return time.Date(2026, 9, 4, 12, 2, 0, 0, time.UTC) }

	require.Equal(t, queryErr, r.RunFast(context.Background()))
}

func TestBranchReconcilerRunsSelectedBranchesConcurrently(t *testing.T) {
	ctrl := gomock.NewController(t)
	branchRepo := mock_repo.NewMockBranchRepo(ctrl)
	infraFacade := mock_facade.NewMockInfraFacade(ctrl)
	branches := []*entity.Branch{
		{ID: "slow", ProjectID: 1, Status: constant.BranchStatusCreating},
		{ID: "fast", ProjectID: 1, Status: constant.BranchStatusCreating},
	}
	branchRepo.EXPECT().ListBranchesByFilter(gomock.Any(), gomock.Any()).Return(branches, nil)

	slowStarted := make(chan struct{})
	slowRelease := make(chan struct{})
	fastStarted := make(chan struct{})
	infraFacade.EXPECT().GetBranch(gomock.Any(), uint64(1), "slow").DoAndReturn(
		func(context.Context, uint64, string) (*cloudv1.Branch, error) {
			close(slowStarted)
			<-slowRelease
			return &cloudv1.Branch{}, nil
		},
	)
	infraFacade.EXPECT().GetBranch(gomock.Any(), uint64(1), "fast").DoAndReturn(
		func(context.Context, uint64, string) (*cloudv1.Branch, error) {
			close(fastStarted)
			return &cloudv1.Branch{}, nil
		},
	)

	r := NewBranchReconciler(infraFacade, branchRepo, nil, nil, nil, nil, nil)
	done := make(chan error, 1)
	go func() { done <- r.RunFast(context.Background()) }()
	<-slowStarted

	select {
	case <-fastStarted:
	case <-time.After(time.Second):
		t.Error("fast branch was blocked behind slow branch")
	}
	close(slowRelease)
	require.NoError(t, <-done)
}
