package approval

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestListUnprojectedPaginatesOneSubjectType(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	db := startApprovalRollbackDB(t, ctx)
	now := time.Now().UTC().Truncate(time.Second)
	fixture := seedApprovalFixture(t, db, "pending-subject-page", now)
	store := New(db)

	create := func(id, subjectType, subjectRef string) Instance {
		t.Helper()
		params := createParams(fixture, id, []string{fixture.ApproverAID}, now)
		params.SubjectType = subjectType
		params.SubjectRef = subjectRef
		params.SubjectVersion = 1
		params.IdempotencyKey = id + "-submission"
		params.PendingKey = id + "-pending"
		instance, _, created, err := store.Create(ctx, params)
		require.NoError(t, err)
		require.True(t, created)
		return instance
	}

	first := create("appr_pending_page_01", "team_role_proposal", "team-page-1")
	create("appr_pending_page_02", "service_account_create_request", "service-account-page")
	second := create("appr_pending_page_03", "team_role_proposal", "team-page-2")

	page, err := store.ListUnprojected(ctx, UnprojectedOptions{
		SubjectType: "team_role_proposal",
		Limit:       1,
	})
	require.NoError(t, err)
	require.Equal(t, []string{first.ID}, instanceIDs(page.Instances))
	require.True(t, page.HasMore)
	require.Equal(t, first.ID, page.NextCursor)

	page, err = store.ListUnprojected(ctx, UnprojectedOptions{
		SubjectType: "team_role_proposal",
		AfterID:     page.NextCursor,
		Limit:       1,
	})
	require.NoError(t, err)
	require.Equal(t, []string{second.ID}, instanceIDs(page.Instances))
	require.False(t, page.HasMore)
	require.Equal(t, second.ID, page.NextCursor)
}

func TestListUnprojectedRetainsTerminalUntilProjectionMarked(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	db := startApprovalRollbackDB(t, ctx)
	now := time.Now().UTC().Truncate(time.Second)
	fixture := seedApprovalFixture(t, db, "pending-subject-terminal-watermark", now)
	store := New(db)

	params := createParams(
		fixture,
		"appr_pending_terminal_watermark",
		[]string{fixture.ApproverAID},
		now.Add(-48*time.Hour),
	)
	params.SubjectType = "team_role_proposal"
	params.SubjectRef = "team-terminal-watermark"
	params.SubjectVersion = 1
	params.IdempotencyKey = "terminal-watermark-submission"
	params.PendingKey = "terminal-watermark-pending"
	instance, assignments, created, err := store.Create(ctx, params)
	require.NoError(t, err)
	require.True(t, created)
	require.Len(t, assignments, 1)

	decided, err := store.Decide(ctx, DecideParams{
		WorkspaceID:      fixture.WorkspaceID,
		ApprovalID:       instance.ID,
		AssignmentID:     assignments[0].ID,
		ActorMemberID:    fixture.ApproverAID,
		ExpectedVersion:  instance.Version,
		ClientDecisionID: "terminal-watermark-decision",
		Decision:         DecisionRejected,
		Reason:           "not now",
		Now:              now.Add(-47 * time.Hour),
	})
	require.NoError(t, err)
	require.Equal(t, StateRejected, decided.Instance.State)

	page, err := store.ListUnprojected(ctx, UnprojectedOptions{
		SubjectType: "team_role_proposal",
	})
	require.NoError(t, err)
	require.Equal(t, []string{instance.ID}, instanceIDs(page.Instances))

	require.NoError(t, store.MarkProjected(
		ctx, fixture.WorkspaceID, instance.ID, decided.Instance.Version,
	))
	require.NoError(t, store.MarkProjected(
		ctx, fixture.WorkspaceID, instance.ID, decided.Instance.Version,
	))
	page, err = store.ListUnprojected(ctx, UnprojectedOptions{
		SubjectType: "team_role_proposal",
	})
	require.NoError(t, err)
	require.Empty(t, page.Instances)
}

func TestMarkProjectedRejectsVersionAheadOfAuthority(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	db := startApprovalRollbackDB(t, ctx)
	now := time.Now().UTC().Truncate(time.Second)
	fixture := seedApprovalFixture(t, db, "projection-version-fence", now)
	store := New(db)
	params := createParams(
		fixture, "appr_projected_version_fence",
		[]string{fixture.ApproverAID}, now,
	)
	instance, _, created, err := store.Create(ctx, params)
	require.NoError(t, err)
	require.True(t, created)

	err = store.MarkProjected(
		ctx, fixture.WorkspaceID, instance.ID, instance.Version+1,
	)
	require.ErrorContains(t, err, "not authoritative")
	loaded, err := store.Load(ctx, fixture.WorkspaceID, instance.ID)
	require.NoError(t, err)
	require.Zero(t, loaded.ProjectedVersion)
}

func instanceIDs(instances []Instance) []string {
	ids := make([]string, 0, len(instances))
	for _, instance := range instances {
		ids = append(ids, instance.ID)
	}
	return ids
}
