package healthtiming

import (
	"database/sql"
	"errors"
	"fmt"
	"testing"
	"time"

	. "github.com/smartystreets/goconvey/convey"
)

type testInnerBackend struct {
	healthMsg   string
	healthError error
	pauseTime   time.Duration
}

func (be *testInnerBackend) Check() (healthyMsg string, err error) {
	time.Sleep(be.pauseTime)
	return be.healthMsg, be.healthError
}

func (be *testInnerBackend) Open() (*sql.DB, error) {
	return nil, nil
}

type testQuery struct {
	datapoint     float64
	datapointTime time.Time
	queryError    error
}

func (qu *testQuery) Execute() (float64, time.Time, error) {
	return qu.datapoint, qu.datapointTime, qu.queryError
}

//Test cases:
// - Inner backend error gets passed through when timing is healthy
// - Inner backend error gets passed through when timing is unhealthy
// - Inner backend healthy gets passed through when absolute is unhealthy but relative is healthy
// - Inner backend healthy gets passed through when absolute is healthy but relative is unhealthy
// - Timing backend raises unhealthy for healthy backend when absolute & relative are unhealthy
// - Inner backend healthy gets passed through when absolute is unhealthy & query raises error
// - Inner backend healthy gets passed through when absolute is unhealthy & relative is unhealthy but stale
func TestTimingBackend(t *testing.T) {
	Convey("With New Timing Backend", t, func(c C) {
		var expectedHealthy string
		var expectedError error
		var innerHealthy string
		var innerError error
		var innerbackendTime time.Duration
		var queryData float64
		var queryTime time.Time = time.Now()
		var queryError error = nil

		Convey("With unhealthy inner backend", func(c C) {
			innerHealthy = ""
			innerError = errors.New("It's an error")
			expectedHealthy = ""
			expectedError = errors.New("It's an error")

			Convey("With Unhealthy Times, Inner Backend Error Passes through", func(c C) {
				innerbackendTime = 60 * time.Millisecond
				queryData = 3.0
			})

			Convey("With Healthy Times, Inner Backend Error Passes through", func(c C) {
				innerbackendTime = 0 * time.Millisecond
				queryData = 1.0
			})
		})

		Convey("With healthy inner backend", func(c C) {
			innerHealthy = "Healthy"
			innerError = nil

			Convey("Pass through inner healthy when ", func(c C) {
				expectedHealthy = "Healthy"
				expectedError = nil

				Convey("Absolute Timing is Healthy But Relative Timing is Unhealthy", func(c C) {
					innerbackendTime = 0 * time.Millisecond
					queryData = 3.0
				})

				Convey("Absolute Timing is Unhealthy But Relative Timing is Unhealthy", func(c C) {
					innerbackendTime = 60 * time.Millisecond
					queryData = 1.0
				})

				Convey("Absolute & Relative Timing are Unhealthy, but ", func(c C) {
					innerbackendTime = 60 * time.Millisecond
					queryData = 3.0

					Convey("Query raised error", func(c C) {
						queryError = errors.New("Something went wrong")
					})

					Convey("Query results were stale", func(c C) {
						queryTime = time.Now().Add(-20 * time.Second)
					})
				})
			})

			Convey("Raise unhealthy timing when absolute & relative timing are unhealthy", func(c C) {
				innerbackendTime = 60 * time.Millisecond
				queryData = 3.0
				expectedHealthy = ""
				expectedError = fmt.Errorf("The health query took longer than %v and was %f times an average of similar healthchecks from other machines.", 50*time.Millisecond, 3.0)
			})
		})

		inner := &testInnerBackend{
			healthMsg:   innerHealthy,
			healthError: innerError,
			pauseTime:   innerbackendTime,
		}

		query := &testQuery{
			datapoint:     queryData,
			datapointTime: queryTime,
			queryError:    queryError,
		}

		timingBackend := NewBackendTimingChecker(inner, 50, 2.0, query)

		//Go's lame & we gotta do ridiculous things to force the goroutine to actually get started
		time.Sleep(10 * time.Millisecond)

		health, err := timingBackend.Check()

		So(health, ShouldResemble, expectedHealthy)
		So(err, ShouldResemble, expectedError)
	})
}
