package cmd

import (
	"fmt"
	"os"
	"strings"
	"sync/atomic"
	"syscall"
	"testing"
	"time"

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

// IsRunning 1 started, 0 not started.
func (c *Cmd) IsRunning() bool {
	return atomic.LoadUint64(&c.isRunning) == 1
}

func (c *Cmd) runApp() {
	fmt.Println("cmd:", append([]string{c.name}, c.args...))
	if atomic.CompareAndSwapUint64(&c.isRunning, 0, 1) {
		c.ErrChan <- c.app.Run()
	}
}

// RunApp exec's the current binary using name as argv[0] which will trigger the
// reexec init function for that name (e.g. "geth-test" in cmd/geth/run_test.go)
func (c *Cmd) RunApp(waitResult func() bool) {
	if waitResult != nil {
		go func() {
			c.runApp()
		}()
		waitResult()
	} else {
		c.runApp()
	}
}

// WaitExit wait util process exit.
func (c *Cmd) WaitExit() {
	if atomic.LoadUint64(&c.isRunning) == 0 {
		return
	}
	// Wait all the check functions are finished, interrupt loop when appear error.
	var err error
	for err == nil && !c.checkFuncs.IsEmpty() {
		select {
		case err = <-c.ErrChan:
			if err != nil {
				fmt.Printf("%s appear error during running, err: %v\n", c.name, err)
			}
		default:
			<-time.After(time.Millisecond * 500)
		}
	}

	// Send interrupt signal.
	_ = c.app.Process.Signal(os.Interrupt)
	// should use `_ = c.app.Process.Wait()` here, but we have some bugs in coordinator's graceful exit,
	// so we use `Kill` as a temp workaround. And since `WaitExit` is only used in integration tests, so
	// it won't really affect our functionalities.
	if err = c.app.Process.Signal(syscall.SIGTERM); err != nil {
		_ = c.app.Process.Kill()
	}
}

// Interrupt send interrupt signal.
func (c *Cmd) Interrupt() {
	c.ErrChan <- c.app.Process.Signal(os.Interrupt)
}

// WaitResult return true when get the keyword during timeout.
func (c *Cmd) WaitResult(t *testing.T, timeout time.Duration, keyword string) bool {
	if keyword == "" {
		return false
	}
	okCh := make(chan struct{}, 1)
	c.RegistFunc(keyword, func(buf string) {
		if strings.Contains(buf, keyword) {
			select {
			case okCh <- struct{}{}:
			default:
				return
			}
		}
	})
	defer c.UnRegistFunc(keyword)
	select {
	case <-okCh:
		return true
	case <-time.After(timeout):
		assert.Fail(t, fmt.Sprintf("didn't get the desired result before timeout, keyword: %s", keyword))
	}
	return false
}

// ExpectWithTimeout wait result during timeout time.
func (c *Cmd) ExpectWithTimeout(t *testing.T, parallel bool, timeout time.Duration, keyword string) {
	if keyword == "" {
		return
	}
	okCh := make(chan struct{}, 1)
	c.RegistFunc(keyword, func(buf string) {
		if strings.Contains(buf, keyword) {
			select {
			case okCh <- struct{}{}:
			default:
				return
			}
		}
	})

	waitResult := func() {
		defer c.UnRegistFunc(keyword)
		select {
		case <-okCh:
			return
		case <-time.After(timeout):
			assert.Fail(t, fmt.Sprintf("didn't get the desired result before timeout, keyword: %s", keyword))
		}
	}

	if parallel {
		go waitResult()
	} else {
		waitResult()
	}
}
