package approvalapp

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/store/approval"
	"github.com/tidbcloud/AgentTeam/internal/workspace/owner"
)

type projectionReconcilerTestStore struct {
	pages map[string][]approval.UnprojectedPage
	calls map[string][]approval.UnprojectedOptions
	marks map[string]int64
}

func (store *projectionReconcilerTestStore) ListUnprojected(
	_ context.Context,
	options approval.UnprojectedOptions,
) (approval.UnprojectedPage, error) {
	store.calls[options.SubjectType] = append(store.calls[options.SubjectType], options)
	pages := store.pages[options.SubjectType]
	if len(pages) == 0 {
		return approval.UnprojectedPage{}, nil
	}
	page := pages[0]
	store.pages[options.SubjectType] = pages[1:]
	return page, nil
}

func (*projectionReconcilerTestStore) ListAssignments(
	context.Context,
	string,
	string,
) ([]approval.Assignment, error) {
	return nil, nil
}

func (store *projectionReconcilerTestStore) Load(
	_ context.Context,
	_, approvalID string,
) (approval.Instance, error) {
	return approval.Instance{ID: approvalID}, nil
}

func (store *projectionReconcilerTestStore) MarkProjected(
	_ context.Context,
	_ string,
	approvalID string,
	version int64,
) error {
	store.marks[approvalID] = version
	return nil
}

// ExpireDueApprovals is the reconciler's lazy settlement sweep (the #t2771
// expiry path). The reconciler calls it once per pass; the test store has no
// due approvals to settle, so it reports zero expired.
func (*projectionReconcilerTestStore) ExpireDueApprovals(
	_ context.Context,
	_ approval.ExpireDueOptions,
) (int, error) {
	return 0, nil
}

func TestProjectionReconcilerKeepsIndependentCursorPerSubjectType(t *testing.T) {
	store := &projectionReconcilerTestStore{
		pages: map[string][]approval.UnprojectedPage{
			"alpha": {
				{
					Instances: []approval.Instance{{
						ID: "appr_alpha_1", WorkspaceID: "wsp_test",
						SubjectType: "alpha", State: approval.StatePending, Version: 1,
					}},
					NextCursor: "appr_alpha_1",
					HasMore:    true,
				},
				{},
			},
			"beta": {
				{
					Instances: []approval.Instance{{
						ID: "appr_beta_1", WorkspaceID: "wsp_test",
						SubjectType: "beta", State: approval.StatePending, Version: 1,
					}},
					NextCursor: "appr_beta_1",
					HasMore:    true,
				},
				{},
			},
		},
		calls: make(map[string][]approval.UnprojectedOptions),
		marks: make(map[string]int64),
	}
	alpha := &subjectRegistryTestHandler{subjectType: "alpha"}
	beta := &subjectRegistryTestHandler{subjectType: "beta"}
	registry := NewSubjectRegistry(store)
	require.NoError(t, registry.Register(alpha))
	require.NoError(t, registry.Register(beta))
	registry.Seal()

	reconciler := NewProjectionReconciler(
		store,
		registry,
		owner.NewAllSet(),
		nil,
	)
	result, err := reconciler.ProcessOnce(context.Background())
	require.NoError(t, err)
	require.Equal(t, 2, result.Scanned)
	require.Equal(t, 2, result.Projected)

	result, err = reconciler.ProcessOnce(context.Background())
	require.NoError(t, err)
	require.Zero(t, result.Scanned)
	require.Len(t, store.calls["alpha"], 2)
	require.Len(t, store.calls["beta"], 2)
	require.Empty(t, store.calls["alpha"][0].AfterID)
	require.Equal(t, "appr_alpha_1", store.calls["alpha"][1].AfterID)
	require.Empty(t, store.calls["beta"][0].AfterID)
	require.Equal(t, "appr_beta_1", store.calls["beta"][1].AfterID)
	require.Equal(t, int64(1), store.marks["appr_alpha_1"])
	require.Equal(t, int64(1), store.marks["appr_beta_1"])
}
