// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package global

import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"testing"
	"time"

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

type errLogger []string

func (l *errLogger) Write(p []byte) (int, error) {
	msg := bytes.TrimRight(p, "\n")
	(*l) = append(*l, string(msg))
	return len(msg), nil
}

func (l *errLogger) Reset() {
	*l = errLogger([]string{})
}

func (l *errLogger) Got() []string {
	return []string(*l)
}

type HandlerTestSuite struct {
	suite.Suite

	origHandler *loggingErrorHandler
	errLogger   *errLogger
}

func (s *HandlerTestSuite) SetupSuite() {
	s.errLogger = new(errLogger)
	s.origHandler = globalErrorHandler
	globalErrorHandler = &loggingErrorHandler{
		l: log.New(s.errLogger, "", 0),
	}
}

func (s *HandlerTestSuite) TearDownSuite() {
	globalErrorHandler = s.origHandler
}

func (s *HandlerTestSuite) SetupTest() {
	s.errLogger.Reset()
}

func (s *HandlerTestSuite) TestGlobalHandler() {
	errs := []string{"one", "two"}
	ErrorHandler().Handle(errors.New(errs[0]))
	Handle(errors.New(errs[1]))
	s.Assert().Equal(errs, s.errLogger.Got())
}

func (s *HandlerTestSuite) TestNoDropsOnDelegate() {
	// max time to wait for goroutine to Handle an error.
	pause := 10 * time.Millisecond

	var sent int
	err := errors.New("")
	stop := make(chan struct{})
	beat := make(chan struct{})
	done := make(chan struct{})

	// Wait for a error to be submitted from the following goroutine.
	wait := func(d time.Duration) error {
		timer := time.NewTimer(d)
		select {
		case <-timer.C:
			// We are about to fail, stop the spawned goroutine.
			stop <- struct{}{}
			return fmt.Errorf("no errors sent in %v", d)
		case <-beat:
			// Allow the timer to be reclaimed by GC.
			timer.Stop()
			return nil
		}
	}

	go func() {
		// Slow down to speed up: do not overload the processor.
		ticker := time.NewTicker(100 * time.Microsecond)
		for {
			select {
			case <-stop:
				ticker.Stop()
				done <- struct{}{}
				return
			case <-ticker.C:
				sent++
				Handle(err)
			}

			select {
			case beat <- struct{}{}:
			default:
			}
		}
	}()

	// Wait for the spice to flow
	s.Require().NoError(wait(pause), "starting error stream")

	// Change to another Handler. We are testing this is loss-less.
	newErrLogger := new(errLogger)
	secondary := &loggingErrorHandler{
		l: log.New(newErrLogger, "", 0),
	}
	SetErrorHandler(secondary)
	s.Require().NoError(wait(pause), "switched to new Handler")

	// Testing done, stop sending errors.
	stop <- struct{}{}
	// Ensure we do not lose any straglers.
	<-done

	got := append(s.errLogger.Got(), newErrLogger.Got()...)
	s.Assert().Greater(len(got), 1, "at least 2 errors should have been sent")
	s.Assert().Len(got, sent)
}

func TestHandlerTestSuite(t *testing.T) {
	suite.Run(t, new(HandlerTestSuite))
}