// Copyright 2024 CloudWeGo Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package versions

import (
	"errors"
	"fmt"
	"os/exec"
	"testing"

	"github.com/cloudwego/kitex"

	"github.com/cloudwego/kitex/internal/test"
)

func TestRegisterMinDepVersion(t *testing.T) {
	refPath := "github.com/cloudwego/kitex"
	testcases := []struct {
		desc      string
		depVer    *MinDepVersion
		expectErr bool
	}{
		{
			desc: "correct MinDepVersion",
			depVer: &MinDepVersion{
				RefPath: refPath,
				Version: "v0.9.0",
			},
		},
		{
			desc: "correct MinDepVersion with prerelease information",
			depVer: &MinDepVersion{
				RefPath: refPath,
				Version: "v0.9.0-rc1",
			},
		},
		{
			desc: "MinDepVersion missing RefPath",
			depVer: &MinDepVersion{
				RefPath: "",
			},
			expectErr: true,
		},
		{
			desc: "MinDepVersion missing Version",
			depVer: &MinDepVersion{
				RefPath: refPath,
				Version: "",
			},
			expectErr: true,
		},
		{
			desc: "MinDepVersion with non-semantic MinimalVersion",
			depVer: &MinDepVersion{
				RefPath: refPath,
				Version: "0.9.0",
			},
			expectErr: true,
		},
	}

	for _, tc := range testcases {
		t.Run(tc.desc, func(t *testing.T) {
			err := RegisterMinDepVersion(tc.depVer)
			if tc.expectErr {
				test.Assert(t, err != nil)
			} else {
				test.Assert(t, err == nil)
			}
		})
	}
}

func TestCheckDependencies(t *testing.T) {
	testcases := []struct {
		desc       string
		depVer     *MinDepVersion
		expectFunc func(t *testing.T, cr *CheckResult)
	}{
		{
			desc: "Dependency is compatible",
			depVer: &MinDepVersion{
				RefPath: "github.com/cloudwego/thriftgo",
				Version: "v0.3.6",
			},
			expectFunc: func(t *testing.T, cr *CheckResult) {
				test.Assert(t, cr != nil)
				test.Assert(t, cr.Err() == nil)
				test.Assert(t, cr.GoModVersion() != "")
				test.Assert(t, cr.MinDepVersion().RefPath == "github.com/cloudwego/thriftgo")
				test.Assert(t, cr.MinDepVersion().Version == "v0.3.6")
			},
		},
		{
			desc: "Dependency is compatible with prerelease information",
			depVer: &MinDepVersion{
				RefPath: "github.com/cloudwego/thriftgo",
				Version: "v0.3.6-rc1",
			},
			expectFunc: func(t *testing.T, cr *CheckResult) {
				test.Assert(t, cr != nil)
				test.Assert(t, cr.Err() == nil)
				test.Assert(t, cr.GoModVersion() != "")
				test.Assert(t, cr.MinDepVersion().RefPath == "github.com/cloudwego/thriftgo")
				test.Assert(t, cr.MinDepVersion().Version == "v0.3.6-rc1")
			},
		},
		{
			desc: "Dependency is not compatible",
			depVer: &MinDepVersion{
				RefPath: "github.com/cloudwego/thriftgo",
				Version: "v999.999.999",
			},
			expectFunc: func(t *testing.T, cr *CheckResult) {
				test.Assert(t, cr != nil)
				test.Assert(t, errors.Is(cr.Err(), ErrDependencyVersionNotCompatible))
				test.Assert(t, cr.GoModVersion() != "")
				test.Assert(t, cr.MinDepVersion().RefPath == "github.com/cloudwego/thriftgo")
				test.Assert(t, cr.MinDepVersion().Version == "v999.999.999")
			},
		},
		{
			desc: "Dependency does not exist",
			depVer: &MinDepVersion{
				RefPath: "xxx/yyy/zzz",
				Version: "v999.999.999",
			},
			expectFunc: func(t *testing.T, cr *CheckResult) {
				test.Assert(t, cr != nil)
				test.Assert(t, errors.Is(cr.Err(), ErrDependencyNotFound))
				test.Assert(t, cr.GoModVersion() == "")
				test.Assert(t, cr.MinDepVersion().RefPath == "xxx/yyy/zzz")
				test.Assert(t, cr.MinDepVersion().Version == "v999.999.999")
			},
		},
	}

	for _, tc := range testcases {
		t.Run(tc.desc, func(t *testing.T) {
			defer func() {
				err := recover()
				test.Assert(t, err == nil, err)
			}()
			RegisterMinDepVersion(tc.depVer)
			res := CheckDependency()
			tc.expectFunc(t, res)
		})
	}
}

func Test_defaultParseCheckResult(t *testing.T) {
	testcases := []struct {
		desc   string
		cr     *CheckResult
		expect func(*testing.T, string, bool)
	}{
		{
			desc: "Kitex is incompatible",
			cr: &CheckResult{
				ver: &MinDepVersion{
					RefPath: "github.com/cloudwego/kitex",
					Version: "v0.9.0",
				},
				goModVer: "v0.8.0",
				err:      ErrDependencyVersionNotCompatible,
			},
			expect: func(t *testing.T, prompt string, shouldExit bool) {
				test.Assert(t, shouldExit == true)
				test.Assert(t, prompt == fmt.Sprintf(`# Kitex Cmd Tool %s is not compatible with github.com/cloudwego/kitex v0.8.0 in your go.mod
# You can upgrade Kitex to latest version
go get github.com/cloudwego/kitex@latest
# Or upgrade Kitex to %s version
go get github.com/cloudwego/kitex@%s
# Or downgrade Kitex Cmd Tool to v0.8.0 version
go install github.com/cloudwego/kitex/tool/cmd/kitex@v0.8.0`, kitex.Version, kitex.Version, kitex.Version))
			},
		},
		{
			desc: "Kitex is compatible",
			cr: &CheckResult{
				ver: &MinDepVersion{
					RefPath: "github.com/cloudwego/kitex",
					Version: "v0.9.0",
				},
				goModVer: "v0.9.1",
			},
			expect: func(t *testing.T, res string, shouldExit bool) {
				test.Assert(t, shouldExit == false)
				test.Assert(t, res == "")
			},
		},
	}

	for _, tc := range testcases {
		t.Run(tc.desc, func(t *testing.T) {
			prompt, shouldExit := defaultParseCheckResult(tc.cr)
			tc.expect(t, prompt, shouldExit)
		})
	}
}

func Test_parseGoToolVersion(t *testing.T) {
	testcases := []struct {
		desc      string
		goListStr string
		expect    string
	}{
		{
			desc:      "normal case without replacement",
			goListStr: "github.com/cloudwego/kitex v0.9.0",
			expect:    "v0.9.0",
		},
		{
			desc:      "normal case with replacement",
			goListStr: "github.com/cloudwego/kitex v0.9.0 => github.com/cloudwego/kitex v0.9.1",
			expect:    "v0.9.1",
		},
		{
			desc:      "replace with local directory",
			goListStr: "github.com/cloudwego/kitex v0.9.0 => ./kitex",
			expect:    "",
		},
	}

	for _, tc := range testcases {
		t.Run(tc.desc, func(t *testing.T) {
			res := parseGoListVersion(tc.goListStr)
			test.Assertf(t, res == tc.expect, "want %s, but got %s", tc.expect, res)
		})
	}
}

func Test_runCommand(t *testing.T) {
	t.Run("cmd not found", func(t *testing.T) {
		res, err := runCommand("cmd_does_not_exist")
		test.Assert(t, errors.Is(err, exec.ErrNotFound), err)
		test.Assert(t, res == "")
	})
}

func Test_runGoListCmd(t *testing.T) {
	t.Run("dependency found", func(t *testing.T) {
		res, err := runGoListCmd("github.com/cloudwego/thriftgo")
		test.Assert(t, err == nil)
		test.Assert(t, res != "")
	})
	t.Run("dependency not found", func(t *testing.T) {
		res, err := runGoListCmd("the/path/that/does/not/exist")
		test.Assert(t, errors.Is(err, ErrDependencyNotFound))
		test.Assert(t, res == "")
	})
}

func Test_generateCheckResult(t *testing.T) {
	testcases := []struct {
		desc   string
		depVer func() *MinDepVersion
		err    error
		expect func(t *testing.T, cr *CheckResult)
	}{
		{
			desc: "err is not nil",
			depVer: func() *MinDepVersion {
				depVer := &MinDepVersion{
					RefPath: "github.com/cloudwego/kitex",
					Version: "v0.9.0",
				}
				_ = depVer.init()
				return depVer
			},
			err: ErrDependencyReplacedWithLocalRepo,
			expect: func(t *testing.T, cr *CheckResult) {
				test.Assert(t, cr != nil)
				test.Assert(t, cr.GoModVersion() == "")
				test.Assert(t, cr.MinDepVersion().RefPath == "github.com/cloudwego/kitex")
				test.Assert(t, cr.MinDepVersion().Version == "v0.9.0")
				test.Assert(t, errors.Is(cr.Err(), ErrDependencyReplacedWithLocalRepo))
			},
		},
		{
			desc: "compatible",
			depVer: func() *MinDepVersion {
				depVer := &MinDepVersion{
					RefPath: "github.com/cloudwego/kitex",
					Version: "v0.9.0",
				}
				_ = depVer.init()
				goModVer, _ := newVersion("v0.9.1")
				depVer.goModVer = goModVer
				return depVer
			},
			expect: func(t *testing.T, cr *CheckResult) {
				test.Assert(t, cr != nil)
				test.Assert(t, cr.GoModVersion() == "v0.9.1")
				test.Assert(t, cr.MinDepVersion().RefPath == "github.com/cloudwego/kitex")
				test.Assert(t, cr.MinDepVersion().Version == "v0.9.0")
				test.Assert(t, cr.Err() == nil)
			},
		},
		{
			desc: "incompatible",
			depVer: func() *MinDepVersion {
				depVer := &MinDepVersion{
					RefPath: "github.com/cloudwego/kitex",
					Version: "v0.9.0",
				}
				_ = depVer.init()
				goModVer, _ := newVersion("v0.8.0")
				depVer.goModVer = goModVer
				return depVer
			},
			expect: func(t *testing.T, cr *CheckResult) {
				test.Assert(t, cr != nil)
				test.Assert(t, cr.GoModVersion() == "v0.8.0")
				test.Assert(t, cr.MinDepVersion().RefPath == "github.com/cloudwego/kitex")
				test.Assert(t, cr.MinDepVersion().Version == "v0.9.0")
				test.Assert(t, errors.Is(cr.Err(), ErrDependencyVersionNotCompatible))
			},
		},
	}

	for _, tc := range testcases {
		t.Run(tc.desc, func(t *testing.T) {
			cr := generateCheckResult(tc.depVer(), tc.err)
			tc.expect(t, cr)
		})
	}
}
