// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package conditions_test

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/suite"

	"github.com/siderolabs/talos/pkg/conditions"
)

type AllSuite struct {
	suite.Suite
}

type MockCondition struct {
	description string
	errCh       chan error
}

func (mc *MockCondition) String() string {
	return mc.description
}

func (mc *MockCondition) Wait(ctx context.Context) error {
	select {
	case <-ctx.Done():
		return ctx.Err()
	case err := <-mc.errCh:
		return err
	}
}

func (suite *AllSuite) TestString() {
	suite.Require().Equal("A, B", conditions.WaitForAll(
		&MockCondition{description: "A"},
		&MockCondition{description: "B"},
	).String())

	suite.Require().Equal("A", conditions.WaitForAll(
		&MockCondition{description: "A"},
	).String())

	conds := []conditions.Condition{
		&MockCondition{description: "A", errCh: make(chan error)},
		&MockCondition{description: "B", errCh: make(chan error)},
	}

	waiter := conditions.WaitForAll(conds...)

	done := make(chan error)

	go func() {
		done <- waiter.Wait(context.Background())
	}()

	suite.Require().Equal("A, B", waiter.String())

	conds[0].(*MockCondition).errCh <- nil

	time.Sleep(50 * time.Millisecond)

	// done waiting for 'A', so description should now be shorter
	suite.Require().Equal("B", waiter.String())

	conds[1].(*MockCondition).errCh <- nil

	<-done
}

func (suite *AllSuite) TestFlatten() {
	conds1 := []conditions.Condition{
		&MockCondition{description: "A", errCh: make(chan error)},
		&MockCondition{description: "B", errCh: make(chan error)},
	}
	conds2 := []conditions.Condition{
		&MockCondition{description: "C", errCh: make(chan error)},
		&MockCondition{description: "D", errCh: make(chan error)},
	}

	waiter := conditions.WaitForAll(conditions.WaitForAll(conds1...), conditions.WaitForAll(conds2...))
	suite.Require().Equal("A, B, C, D", waiter.String())

	ctx, ctxCancel := context.WithCancel(context.Background())
	defer ctxCancel()

	done := make(chan error)

	go func() {
		done <- waiter.Wait(ctx)
	}()

	conds1[0].(*MockCondition).errCh <- nil

	conds2[1].(*MockCondition).errCh <- nil

	time.Sleep(50 * time.Millisecond)

	suite.Require().Equal("B, C", waiter.String())

	ctxCancel()

	suite.Require().Equal(context.Canceled, <-done)
}

func TestAllSuite(t *testing.T) {
	suite.Run(t, new(AllSuite))
}
