package jobs_test

import (
	"context"
	"io"
	"log/slog"
	"sync/atomic"
	"testing"
	"time"

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

	"github.com/yan1h/agent-coding-workflow/internal/jobs"
)

func discardLogger() *slog.Logger {
	return slog.New(slog.NewTextHandler(io.Discard, nil))
}

func TestScheduler_TickerFiresJob(t *testing.T) {
	t.Parallel()
	var calls atomic.Int32
	s := jobs.NewScheduler(discardLogger())
	s.Add("test", 20*time.Millisecond, true, func(ctx context.Context) {
		calls.Add(1)
	})

	ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
	defer cancel()
	s.Start(ctx)

	<-ctx.Done()
	s.Wait()
	assert.GreaterOrEqual(t, calls.Load(), int32(3), "expected at least 3 ticks in 200ms with 20ms interval")
}

func TestScheduler_DisabledNotRun(t *testing.T) {
	t.Parallel()
	var calls atomic.Int32
	s := jobs.NewScheduler(discardLogger())
	s.Add("disabled", 10*time.Millisecond, false, func(ctx context.Context) {
		calls.Add(1)
	})
	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
	defer cancel()
	s.Start(ctx)
	<-ctx.Done()
	s.Wait()
	assert.Equal(t, int32(0), calls.Load())
}

func TestScheduler_StopOnCancel(t *testing.T) {
	t.Parallel()
	var calls atomic.Int32
	s := jobs.NewScheduler(discardLogger())
	s.Add("a", 5*time.Millisecond, true, func(ctx context.Context) { calls.Add(1) })
	ctx, cancel := context.WithCancel(context.Background())
	s.Start(ctx)
	time.Sleep(30 * time.Millisecond)
	cancel()
	s.Wait()
	first := calls.Load()
	time.Sleep(20 * time.Millisecond)
	require.Equal(t, first, calls.Load(), "ticks must stop after cancel")
}

func TestScheduler_RunnerPanicRecovered(t *testing.T) {
	t.Parallel()
	var calls atomic.Int32
	s := jobs.NewScheduler(discardLogger())
	s.Add("panicky", 10*time.Millisecond, true, func(ctx context.Context) {
		calls.Add(1)
		panic("boom")
	})
	ctx, cancel := context.WithTimeout(context.Background(), 80*time.Millisecond)
	defer cancel()
	s.Start(ctx)
	<-ctx.Done()
	s.Wait()
	assert.GreaterOrEqual(t, calls.Load(), int32(2), "next tick should fire even if previous panicked")
}
