package repository

import (
	"context"
	"sync"
	"testing"
	"time"

	"apps/backend/internal/module/studio/domain"

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

func TestOutboxClaimIsAtomicPerStep(t *testing.T) {
	repo := NewMemory()
	now := domain.NowNano()
	require.NoError(t, repo.SaveOutbox(context.Background(), &domain.OutboxBundle{
		ID: "bundle", Steps: []domain.OutboxStep{{ID: "step", Status: domain.StepScheduled, ScheduledAt: now}},
	}))

	const workers = 16
	start := make(chan struct{})
	results := make(chan error, workers)
	var wg sync.WaitGroup
	for i := 0; i < workers; i++ {
		wg.Add(1)
		go func(owner string) {
			defer wg.Done()
			<-start
			_, err := repo.ClaimOutboxStep(context.Background(), "bundle", "step", owner, now, now+int64(time.Minute))
			results <- err
		}(time.Duration(i).String())
	}
	close(start)
	wg.Wait()
	close(results)

	claimed := 0
	for err := range results {
		if err == nil {
			claimed++
		} else {
			require.ErrorIs(t, err, domain.ErrIllegalStatus)
		}
	}
	require.Equal(t, 1, claimed)
}

func TestOutboxExpiredClaimFencesStaleOwner(t *testing.T) {
	repo := NewMemory()
	now := domain.NowNano()
	require.NoError(t, repo.SaveOutbox(context.Background(), &domain.OutboxBundle{
		ID: "bundle", Steps: []domain.OutboxStep{{ID: "step", Kind: domain.StepRoot, Status: domain.StepScheduled, ScheduledAt: now}},
	}))
	first, err := repo.ClaimOutboxStep(context.Background(), "bundle", "step", "worker-a", now, now+10)
	require.NoError(t, err)
	second, err := repo.ClaimOutboxStep(context.Background(), "bundle", "step", "worker-b", now+11, now+int64(time.Minute))
	require.NoError(t, err)

	staleResult := first.Steps[0]
	staleResult.Status = domain.StepPublished
	err = repo.FinishOutboxStep(context.Background(), "bundle", "step", "worker-a", &staleResult, now+12)
	require.ErrorIs(t, err, domain.ErrLeaseLost)

	result := second.Steps[0]
	result.Status, result.MediaID = domain.StepPublished, "media-b"
	require.NoError(t, repo.FinishOutboxStep(context.Background(), "bundle", "step", "worker-b", &result, now+12))
	got, err := repo.GetOutbox(context.Background(), "bundle")
	require.NoError(t, err)
	require.Equal(t, "media-b", got.Steps[0].MediaID)
}

func TestDeleteOutboxRejectsPublishingStep(t *testing.T) {
	repo := NewMemory()
	require.NoError(t, repo.SaveOutbox(context.Background(), &domain.OutboxBundle{
		ID: "bundle", Steps: []domain.OutboxStep{{ID: "step", Status: domain.StepPublishing}},
	}))
	require.ErrorIs(t, repo.DeleteOutbox(context.Background(), "bundle"), domain.ErrIllegalStatus)
	_, err := repo.GetOutbox(context.Background(), "bundle")
	require.NoError(t, err)
}
