package multistep

import (
	"os"
	"reflect"
	"testing"
	"time"
)

func TestDebugRunner_Impl(t *testing.T) {
	var raw interface{}
	raw = &DebugRunner{}
	if _, ok := raw.(Runner); !ok {
		t.Fatal("DebugRunner must be a runner.")
	}
}

func TestDebugRunner_Run(t *testing.T) {
	data := new(BasicStateBag)
	stepA := &TestStepAcc{Data: "a"}
	stepB := &TestStepAcc{Data: "b"}

	pauseFn := func(loc DebugLocation, name string, state StateBag) {
		key := "data"
		if loc == DebugLocationBeforeCleanup {
			key = "cleanup"
		}

		if _, ok := state.GetOk(key); !ok {
			state.Put(key, make([]string, 0, 5))
		}

		data := state.Get(key).([]string)
		state.Put(key, append(data, name))
	}

	r := &DebugRunner{
		Steps:   []Step{stepA, stepB},
		PauseFn: pauseFn,
	}

	r.Run(data)

	// Test data
	expected := []string{"a", "TestStepAcc", "b", "TestStepAcc"}
	results := data.Get("data").([]string)
	if !reflect.DeepEqual(results, expected) {
		t.Errorf("unexpected results: %#v", results)
	}

	// Test cleanup
	expected = []string{"TestStepAcc", "b", "TestStepAcc", "a"}
	results = data.Get("cleanup").([]string)
	if !reflect.DeepEqual(results, expected) {
		t.Errorf("unexpected results: %#v", results)
	}
}

// confirm that can't run twice
func TestDebugRunner_Run_Run(t *testing.T) {
	defer func() {
		recover()
	}()
	ch := make(chan chan bool)
	stepInt := &TestStepSync{ch}
	stepWait := &TestStepWaitForever{}
	r := &DebugRunner{Steps: []Step{stepInt, stepWait}}

	go r.Run(new(BasicStateBag))
	// wait until really running
	<-ch

	// now try to run aain
	r.Run(new(BasicStateBag))

	// should not get here in nominal codepath
	t.Errorf("Was able to run an already running DebugRunner")
}

func TestDebugRunner_Cancel(t *testing.T) {
	ch := make(chan chan bool)
	data := new(BasicStateBag)
	stepA := &TestStepAcc{Data: "a"}
	stepB := &TestStepAcc{Data: "b"}
	stepInt := &TestStepSync{ch}
	stepC := &TestStepAcc{Data: "c"}

	r := &DebugRunner{}
	r.Steps = []Step{stepA, stepB, stepInt, stepC}

	// cancelling an idle Runner is a no-op
	r.Cancel()

	go r.Run(data)

	// Wait until we reach the sync point
	responseCh := <-ch

	// Cancel then continue chain
	cancelCh := make(chan bool)
	go func() {
		r.Cancel()
		cancelCh <- true
	}()

	for {
		if _, ok := data.GetOk(StateCancelled); ok {
			responseCh <- true
			break
		}

		time.Sleep(10 * time.Millisecond)
	}

	<-cancelCh

	// Test run data
	expected := []string{"a", "b"}
	results := data.Get("data").([]string)
	if !reflect.DeepEqual(results, expected) {
		t.Errorf("unexpected result: %#v", results)
	}

	// Test cleanup data
	expected = []string{"b", "a"}
	results = data.Get("cleanup").([]string)
	if !reflect.DeepEqual(results, expected) {
		t.Errorf("unexpected result: %#v", results)
	}

	// Test that it says it is cancelled
	cancelled := data.Get(StateCancelled).(bool)
	if !cancelled {
		t.Errorf("not cancelled")
	}
}

func TestDebugPauseDefault(t *testing.T) {

	// Create a pipe pair so that writes/reads are blocked until we do it
	r, w, err := os.Pipe()
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Set stdin so we can control it
	oldStdin := os.Stdin
	os.Stdin = r
	defer func() { os.Stdin = oldStdin }()

	// Start pausing
	complete := make(chan bool, 1)
	go func() {
		dr := &DebugRunner{Steps: []Step{
			&TestStepAcc{Data: "a"},
		}}
		dr.Run(new(BasicStateBag))
		complete <- true
	}()

	select {
	case <-complete:
		t.Fatal("shouldn't have completed")
	case <-time.After(100 * time.Millisecond):
	}

	w.Write([]byte("\n\n"))

	select {
	case <-complete:
	case <-time.After(100 * time.Millisecond):
		t.Fatal("didn't complete")
	}
}