package workloadnodelocal

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/pingcap/test-infra/sdk/core"
	"github.com/pingcap/test-infra/sdk/e2etest/framework"
	"github.com/pingcap/test-infra/sdk/resource"
	_ "github.com/pingcap/test-infra/sdk/resource/impl/local"
)

var (
	suiteTestCtx core.TestContext
)

var _ = Describe("WorkloadNode Local", func() {
	suiteTestCtx, err := core.BuildContext()
	if err != nil {
		panic("Init test context failed.")
	}
	BeforeEach(func() {
		os.Setenv("TI_PARAM_RES_gotpc1", `{"provider":"github.com/pingcap/test-infra/sdk/resource/impl/local.WorkloadNode", "name": "gotpc1","type":"WORKLOAD_NODE","ready":true}`)
		os.Setenv("TI_PARAM_RES_gotpc1_TIDB_URL", `["mysql://root:@127.0.0.1:4000"]`)
		os.Setenv("TI_PARAM_RES_gotpc1_TIKV_URL", `["http://127.0.0.1:20160"]`)
		os.Setenv("TI_PARAM_RES_gotpc1_PD_URL", `["http://127.0.0.1:2379"]`)
		os.Setenv("TI_PARAM_RES_gotpc1_TIFLASH_URL", `["127.0.0.1:3930"]`)
		os.Setenv("TI_PARAM_RES_gotpc1_PROMETHEUS_URL", `["http://127.0.0.1:9090"]`)
	})
	AfterEach(func() {
		os.Unsetenv("TI_PARAM_RES_gotpc1")
		os.Unsetenv("TI_PARAM_RES_gotpc1_TIDB_URL")
		os.Unsetenv("TI_PARAM_RES_gotpc1_TIKV_URL")
		os.Unsetenv("TI_PARAM_RES_gotpc1_PD_URL")
		os.Unsetenv("TI_PARAM_RES_gotpc1_TIFLASH_URL")
		os.Unsetenv("TI_PARAM_RES_gotpc1_PROMETHEUS_URL")
	})

	Context("WorkloadNode Local.ExecSync", func() {
		It("should work", func() {
			ctx := suiteTestCtx
			gotpc1 := ctx.Resource("gotpc1").(resource.WorkloadNode)
			Expect(gotpc1.Handle().Name).Should(Equal("gotpc1"))

			stdout, stderr, exitcode, err := gotpc1.Exec(resource.WorkloadNodeExecOptions{Command: `echo "hello from stderr" 1>&2; echo "hello from stdout"`})
			Expect(stdout).Should(Equal("hello from stdout\n"))
			Expect(stderr).Should(Equal("hello from stderr\n"))
			Expect(exitcode).Should(Equal(0))

			framework.ExpectNoError(err)
		})

		It("should return correct exitcode", func() {
			ctx := suiteTestCtx
			gotpc1 := ctx.Resource("gotpc1").(resource.WorkloadNode)
			Expect(gotpc1.Handle().Name).Should(Equal("gotpc1"))

			stdout, stderr, exitcode, err := gotpc1.Exec(resource.WorkloadNodeExecOptions{Command: `cat nonexistfile`})
			Expect(stdout).Should(Equal(""))
			Expect(stderr).Should(Equal("cat: nonexistfile: No such file or directory\n"))
			Expect(exitcode).Should(Equal(1))

			framework.ExpectError(err)
		})
	})

	Context("WorkloadNode Local.ExecAsync", func() {
		It("ExecAsync should work well", func() {
			ctx := suiteTestCtx
			gotpc1 := ctx.Resource("gotpc1").(resource.WorkloadNode)
			Expect(gotpc1.Handle().Name).Should(Equal("gotpc1"))

			writer := bytes.NewBuffer(make([]byte, 0, 100))
			rcmd, err := gotpc1.ExecAsync(resource.NopCloser(writer), os.Stderr, resource.WorkloadNodeExecOptions{Command: `for i in $(seq 1 5); do echo $i; sleep 0.25; done`})
			framework.ExpectNoError(err)
			time.Sleep(time.Millisecond * 100)
			for i := 1; i <= 3; i++ {
				n, err := writer.ReadString('\n')
				framework.ExpectNoError(err)
				Expect(n).Should(Equal(fmt.Sprintf("%d\n", i)))
				time.Sleep(time.Millisecond * 250)
			}
			errCmd := rcmd.Wait()
			Expect(writer.String()).Should(Equal("4\n5\n"))
			framework.ExpectNoError(errCmd.Err)
			Expect(errCmd.ExitCode).Should(Equal(0))
		})

		It("ExecAsync should return correct exitcode", func() {
			ctx := suiteTestCtx
			gotpc1 := ctx.Resource("gotpc1").(resource.WorkloadNode)
			Expect(gotpc1.Handle().Name).Should(Equal("gotpc1"))

			writer := bytes.NewBuffer(make([]byte, 0, 100))
			rcmd, err := gotpc1.ExecAsync(os.Stdout, resource.NopCloser(writer), resource.WorkloadNodeExecOptions{Command: `cat nonexistfile`})
			framework.ExpectNoError(err)

			errCmd := rcmd.Wait()
			Expect(writer.String()).Should(Equal("cat: nonexistfile: No such file or directory\n"))
			Expect(errCmd.ExitCode).Should(Equal(1))
			framework.ExpectError(errCmd.Err)

		})
	})

	Context("WorkloadNode Local.ExecAsyncInterruptible", func() {
		It("ExecAsyncInterruptible should work well", func() {
			ctx := suiteTestCtx
			gotpc1 := ctx.Resource("gotpc1").(resource.WorkloadNode)
			Expect(gotpc1.Handle().Name).Should(Equal("gotpc1"))

			writer := bytes.NewBuffer(make([]byte, 0, 100))
			rcmd, err := gotpc1.ExecAsyncInterruptible(resource.NopCloser(writer), resource.WorkloadNodeExecOptions{Command: `for i in $(seq 1 5); do echo $i; sleep 0.25; done`})
			framework.ExpectNoError(err)
			time.Sleep(time.Millisecond * 100)
			for i := 1; i <= 3; i++ {
				n, err := writer.ReadString('\n')
				framework.ExpectNoError(err)
				Expect(n).Should(Equal(fmt.Sprintf("%d\n", i)))
				time.Sleep(time.Millisecond * 250)
			}

			errCmd := rcmd.Wait()
			framework.ExpectNoError(errCmd.Err)
			Expect(writer.String()).Should(Equal("4\n5\n"))
			Expect(errCmd.ExitCode).Should(Equal(0))
		})

		It("ExecAsyncInterruptible should return correct exitcode", func() {
			ctx := suiteTestCtx
			gotpc1 := ctx.Resource("gotpc1").(resource.WorkloadNode)
			Expect(gotpc1.Handle().Name).Should(Equal("gotpc1"))

			writer := bytes.NewBuffer(make([]byte, 0, 100))
			rcmd, err := gotpc1.ExecAsyncInterruptible(resource.NopCloser(writer), resource.WorkloadNodeExecOptions{Command: `cat nonexistfile`})
			framework.ExpectNoError(err)

			errCmd := rcmd.Wait()
			framework.ExpectError(errCmd.Err)
			Expect(writer.String()).Should(Equal("cat: nonexistfile: No such file or directory\n"))
			Expect(errCmd.ExitCode).Should(Equal(1))
		})

		It("ExecAsyncInterruptible should be interruptible", func() {
			ctx := suiteTestCtx
			gotpc1 := ctx.Resource("gotpc1").(resource.WorkloadNode)
			Expect(gotpc1.Handle().Name).Should(Equal("gotpc1"))

			writer := bytes.NewBuffer(make([]byte, 0, 100))
			rcmd, err := gotpc1.ExecAsyncInterruptible(resource.NopCloser(writer), resource.WorkloadNodeExecOptions{Command: `for i in $(seq 1 5); do echo $i; sleep 0.25; done`})
			framework.ExpectNoError(err)
			go func() {
				time.Sleep(time.Millisecond * 800)
				err = rcmd.Interrupt()
			}()
			time.Sleep(time.Millisecond * 100)
			for i := 1; i <= 3; i++ {
				n, err := writer.ReadString('\n')
				framework.ExpectNoError(err)
				Expect(n).Should(Equal(fmt.Sprintf("%d\n", i)))
				time.Sleep(time.Millisecond * 250)
			}

			errCmd := rcmd.Wait()
			framework.ExpectError(errCmd.Err)
			Expect(writer.String()).Should(Equal("4\n"))
			Expect(errCmd.ExitCode).Should(Equal(-1))
			Expect(errCmd.Err.Error()).Should(Equal("signal: terminated"))
		})
	})

	Context("WorkloadNode Local.WriteFile", func() {
		It("WriteFile should work well", func() {
			ctx := suiteTestCtx
			gotpc1 := ctx.Resource("gotpc1").(resource.WorkloadNode)

			framework.ExpectNoError(ioutil.WriteFile("tmp", []byte("hello"), 0644))
			f, err := os.OpenFile("tmp", os.O_RDONLY, 0600)
			framework.ExpectNoError(err)
			gotpc1.WriteFile(resource.WorkloadNodeWriteFileOptions{
				FilePath: "writefilepath",
				Content:  f,
			})
			defer os.Remove("tmp")
			defer f.Close()

			rd, err := os.OpenFile("writefilepath", os.O_RDONLY, 0600)
			defer os.Remove("writefilepath")
			defer rd.Close()
			framework.ExpectNoError(err)
			b, _ := ioutil.ReadAll(rd)
			Expect(string(b)).Should(Equal("hello"))
		})
	})

})
