package version_test

import (
	context "context"
	"encoding/json"
	"fmt"
	"runtime"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"

	"git.cw.tr/mukan-network/mukan-sdk/client/flags"
	"git.cw.tr/mukan-network/mukan-sdk/testutil"
	"git.cw.tr/mukan-network/mukan-sdk/testutil/cmdtest"
	"git.cw.tr/mukan-network/mukan-sdk/version"
)

func TestNewInfo(t *testing.T) {
	info := version.NewInfo()
	want := fmt.Sprintf(`: 
git commit: 
build tags: 
%s`, fmt.Sprintf("go version %s %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH))
	require.Equal(t, want, info.String())
}

func TestInfo_String(t *testing.T) {
	info := version.Info{
		Name:             "testapp",
		AppName:          "testappd",
		Version:          "1.0.0",
		GitCommit:        "1b78457135a4104bc3af97f20654d49e2ea87454",
		BuildTags:        "netgo,ledger",
		GoVersion:        "go version go1.14 linux/amd64",
		CosmosSdkVersion: "0.42.5",
	}
	want := `testapp: 1.0.0
git commit: 1b78457135a4104bc3af97f20654d49e2ea87454
build tags: netgo,ledger
go version go1.14 linux/amd64`
	require.Equal(t, want, info.String())
}

func TestCLI(t *testing.T) {
	setVersionPackageVars(t)

	sys := cmdtest.NewSystem()
	sys.AddCommands(version.NewVersionCommand())

	t.Run("no flags", func(t *testing.T) {
		res := sys.MustRun(t, "version")

		// Only prints the version, with a newline, to stdout.
		require.Equal(t, testVersion+"\n", res.Stdout.String())
		require.Empty(t, res.Stderr.String())
	})

	t.Run("--long flag", func(t *testing.T) {
		res := sys.MustRun(t, "version", "--long")

		out := res.Stdout.String()
		lines := strings.Split(out, "\n")
		require.Contains(t, lines, "name: testchain-app")
		require.Contains(t, lines, "server_name: testchaind")
		require.Contains(t, lines, `version: "3.14"`)
		require.Contains(t, lines, "commit: abc123")
		require.Contains(t, lines, "build_tags: mybuildtag")

		require.Empty(t, res.Stderr.String())
	})

	t.Run("--output=json flag", func(t *testing.T) {
		res := sys.MustRun(t, "version", "--output=json")

		var info version.Info
		require.NoError(t, json.Unmarshal(res.Stdout.Bytes(), &info))

		// Assert against a couple fields that are difficult to predict in test
		// without copying and pasting code.
		require.NotEmpty(t, info.GoVersion)

		// The SDK version appears to not be set during this test, so we'll ignore it here.

		// Now clear out the non-empty fields, so we can compare against a fixed value.
		info.GoVersion = ""

		want := version.Info{
			Name:      testName,
			AppName:   testAppName,
			Version:   testVersion,
			GitCommit: testCommit,
			BuildTags: testBuildTags,
		}
		require.Equal(t, want, info)

		require.Empty(t, res.Stderr.String())
	})

	t.Run("positional args rejected", func(t *testing.T) {
		res := sys.Run("version", "foo")
		require.Error(t, res.Err)
	})
}

const (
	testName      = "testchain-app"
	testAppName   = "testchaind"
	testVersion   = "3.14"
	testCommit    = "abc123"
	testBuildTags = "mybuildtag"
)

// setVersionPackageVars temporarily overrides the package variables in the version package
// so that we can assert meaningful output.
func setVersionPackageVars(t *testing.T) {
	t.Helper()

	var (
		origName      = version.Name
		origAppName   = version.AppName
		origVersion   = version.Version
		origCommit    = version.Commit
		origBuildTags = version.BuildTags
	)

	t.Cleanup(func() {
		version.Name = origName
		version.AppName = origAppName
		version.Version = origVersion
		version.Commit = origCommit
		version.BuildTags = origBuildTags
	})

	version.Name = testName
	version.AppName = testAppName
	version.Version = testVersion
	version.Commit = testCommit
	version.BuildTags = testBuildTags
}

func Test_runVersionCmd(t *testing.T) {
	cmd := version.NewVersionCommand()
	_, mockOut := testutil.ApplyMockIO(cmd)

	cmd.SetArgs([]string{
		fmt.Sprintf("--%s=''", flags.FlagOutput),
		"--long=false",
	})

	require.NoError(t, cmd.Execute())
	require.Equal(t, "\n", mockOut.String())
	mockOut.Reset()

	cmd.SetArgs([]string{
		fmt.Sprintf("--%s=json", flags.FlagOutput), "--long=true",
	})

	info := version.NewInfo()
	stringInfo, err := json.Marshal(info)

	extraInfo := &version.ExtraInfo{"key1": "value1"}
	ctx := context.WithValue(context.Background(), version.ContextKey{}, extraInfo)

	require.NoError(t, err)
	require.NoError(t, cmd.Execute())

	extraInfoFromContext := ctx.Value(version.ContextKey{})
	require.NotNil(t, extraInfoFromContext)

	castedExtraInfo, ok := extraInfoFromContext.(*version.ExtraInfo)
	require.True(t, ok)

	key1Value := (*castedExtraInfo)["key1"]
	require.Equal(t, "value1", key1Value)

	require.Equal(t, string(stringInfo)+"\n", mockOut.String())
}
