package forcedstatus

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

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

type testWatcher struct {
	statusData map[string]*administrativeStatus
}

func (tw *testWatcher) addStatus(scope string, reason string, healthy bool) {
	tw.statusData[scope] = &administrativeStatus{
		healthy:      healthy,
		healthReason: reason,
	}
}

func (tw *testWatcher) Close() error {
	return nil
}

func (tw *testWatcher) GetStatus(scope string) *administrativeStatus {
	data, _ := tw.statusData[scope]
	return data
}

func makeTestBackend(healthyReason string, err error) *testBackend {
	return &testBackend{
		healthyReason: healthyReason,
		err:           err,
		eventSink:     make(chan int, 100),
	}
}

type testBackend struct {
	healthyReason string
	err           error

	eventSink chan int
}

func (tb *testBackend) Open() (*sql.DB, error) {
	return nil, nil
}

func (tb *testBackend) Check() (string, error) {
	tb.eventSink <- 100
	time.Sleep(50 * time.Millisecond)
	tb.eventSink <- 101
	return tb.healthyReason, tb.err
}

/*
Test cases:
 - If forcedchecker has no port, verify inner backend is called inline & is passthrough
 - If forcedchecker has different port than one being queried, verify inner backend is called inline & is passthrough
 - If forcedchecker has same port being queried, verify inner backend is called async & not returned
 - Again with healthy
*/
func TestForcedBackendChecker(t *testing.T) {
	Convey("New Forced Backend Checker", t, func(c C) {

		watcher := &testWatcher{
			statusData: make(map[string]*administrativeStatus),
		}

		var healthReason string
		var err error

		var expectedAsync bool
		var expectedHealthReason string
		var expectedHealthError error

		Convey("When forcing no port", func() {
			expectedAsync = false

			Convey("Healthy internal backend", func() {
				healthReason = "It's healthy"
				err = nil

				expectedHealthReason = "It's healthy"
				expectedHealthError = nil
			})

			Convey("Unhealthy internal backend", func() {
				healthReason = ""
				err = fmt.Errorf("It's unhealthy")

				expectedHealthError = err
				expectedHealthReason = ""
			})
		})

		Convey("When forcing wrong port", func() {
			watcher.addStatus("6432", "This shouldn't happen", false)
			expectedAsync = false

			Convey("Healthy internal backend", func() {
				healthReason = "It's healthy"
				err = nil

				expectedHealthReason = "It's healthy"
				expectedHealthError = nil
			})

			Convey("Unhealhty internal backend", func() {
				healthReason = ""
				err = fmt.Errorf("It's unhealthy")

				expectedHealthError = err
				expectedHealthReason = ""
			})
		})

		Convey("When forcing queried port", func() {
			expectedAsync = true
						
			Convey("Force healthy", func() {
				watcher.addStatus("5432", "This should be healthy", true)
				expectedHealthReason = "Administrator marked healthy: This should be healthy"
				expectedHealthError = nil

				Convey("Healthy internal backend", func() {
					healthReason = "It's healthy"
					err = nil
				})
				Convey("Unhealthy internal backend", func() {
					healthReason = ""
					err = fmt.Errorf("It's unhealthy")
				})
			})

			Convey("Force unhealthy", func() {
				watcher.addStatus("5432", "This should be unhealthy", false)
				expectedHealthReason = ""
				expectedHealthError = fmt.Errorf("Administrator marked unhealthy: This should be unhealthy")

				Convey("Healthy internal backend", func() {
					healthReason = "It's healthy"
					err = nil
				})
				Convey("Unhealthy internal backend", func() {
					healthReason = ""
					err = fmt.Errorf("It's unhealthy")
				})
			})
		})

		backend := makeTestBackend(healthReason, err)
		checker := NewForcedStatusChecker(backend, watcher, 5432)

		backend.eventSink <- 1
		actualHealthReason, actualHealthError := checker.Check()
		backend.eventSink <- 2

		if expectedAsync {
			evt := <-backend.eventSink
			So(evt, ShouldEqual, 1)

			evt = <-backend.eventSink
			So(evt == 2 || evt == 100, ShouldBeTrue)

			evt = <-backend.eventSink
			So(evt == 2 || evt == 100, ShouldBeTrue)

			evt = <-backend.eventSink
			So(evt, ShouldEqual, 101)
		} else {
			evt := <-backend.eventSink
			So(evt, ShouldEqual, 1)
			evt = <-backend.eventSink
			So(evt, ShouldEqual, 100)
			evt = <-backend.eventSink
			So(evt, ShouldEqual, 101)
			evt = <-backend.eventSink
			So(evt, ShouldEqual, 2)
		}

		So(actualHealthError, ShouldResemble, expectedHealthError)
		So(actualHealthReason, ShouldEqual, expectedHealthReason)
	})
}
