package conc

import (
	"sync"
	"testing"

	"github.com/stretchr/testify/suite"
	"go.uber.org/atomic"
)

type SingleflightSuite struct {
	suite.Suite
}

func (s *SingleflightSuite) TestDo() {
	counter, hasShared := atomic.Int32{}, atomic.Bool{}

	sf := Singleflight[any]{}
	ch := make(chan struct{})
	var wg sync.WaitGroup
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go func(i int) {
			defer wg.Done()
			_, _, shared := sf.Do("test_do", func() (any, error) {
				<-ch
				counter.Add(1)
				return struct{}{}, nil
			})
			if shared {
				hasShared.Store(true)
			}
		}(i)
	}
	close(ch)
	wg.Wait()
	if hasShared.Load() {
		s.Less(counter.Load(), int32(10))
	}
}

func (s *SingleflightSuite) TestDoChan() {
	counter, hasShared := atomic.Int32{}, atomic.Bool{}

	sf := Singleflight[any]{}
	ch := make(chan struct{})
	var wg sync.WaitGroup
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go func(i int) {
			defer wg.Done()
			ch := sf.DoChan("test_dochan", func() (any, error) {
				<-ch
				counter.Add(1)
				return struct{}{}, nil
			})
			result := <-ch
			if result.Shared {
				hasShared.Store(true)
			}
		}(i)
	}
	close(ch)
	wg.Wait()
	if hasShared.Load() {
		s.Less(counter.Load(), int32(10))
	}
}

func (s *SingleflightSuite) TestForget() {
	sf := Singleflight[any]{}
	ch := make(chan struct{})
	submitted := make(chan struct{})
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		sf.Do("test_forget", func() (any, error) {
			defer wg.Done()
			close(submitted)
			<-ch
			return struct{}{}, nil
		})
	}()

	<-submitted
	flag := false
	sf.Forget("test_forget")
	sf.Do("test_forget", func() (any, error) {
		flag = true
		return struct{}{}, nil
	})

	close(ch)
	wg.Wait()

	s.True(flag, "new job shall be executed after forget")
}

func TestSingleFlight(t *testing.T) {
	suite.Run(t, new(SingleflightSuite))
}
