// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package versions_test

import (
	"go/ast"
	"go/parser"
	"go/token"
	"go/types"
	"testing"

	"golang.org/x/tools/internal/versions"
)

func TestIsValid(t *testing.T) {
	// valid versions
	for _, x := range []string{
		"go1.21",
		"go1.21.2",
		"go1.21rc",
		"go1.21rc2",
		"go0.0", // ??
		"go1",
		"go2",
		"go1.20.0-bigcorp",
	} {
		if !versions.IsValid(x) {
			t.Errorf("expected versions.IsValid(%q) to hold", x)
		}
	}

	// invalid versions
	for _, x := range []string{
		"",
		"bad",
		"1.21",
		"v1.21",
		"go",
		"goAA",
		"go2_3",
		"go1.BB",
		"go1.21.",
		"go1.21.2_2",
		"go1.21rc_2",
		"go1.21rc2_",
		"go1.600+auto",
	} {
		if versions.IsValid(x) {
			t.Errorf("expected versions.IsValid(%q) to not hold", x)
		}
	}
}

func TestVersionComparisons(t *testing.T) {
	for _, item := range []struct {
		x, y string
		want int
	}{
		// All comparisons of go2, go1.21.2, go1.21rc2, go1.21rc2, go1, go0.0, "", bad
		{"go2", "go2", 0},
		{"go2", "go1.21.2", +1},
		{"go2", "go1.21rc2", +1},
		{"go2", "go1.21rc", +1},
		{"go2", "go1.21", +1},
		{"go2", "go1", +1},
		{"go2", "go0.0", +1},
		{"go2", "", +1},
		{"go2", "bad", +1},
		{"go1.21.2", "go1.21.2", 0},
		{"go1.21.2", "go1.21rc2", +1},
		{"go1.21.2", "go1.21rc", +1},
		{"go1.21.2", "go1.21", +1},
		{"go1.21.2", "go1", +1},
		{"go1.21.2", "go0.0", +1},
		{"go1.21.2", "", +1},
		{"go1.21.2", "bad", +1},
		{"go1.21rc2", "go1.21rc2", 0},
		{"go1.21rc2", "go1.21rc", +1},
		{"go1.21rc2", "go1.21", +1},
		{"go1.21rc2", "go1", +1},
		{"go1.21rc2", "go0.0", +1},
		{"go1.21rc2", "", +1},
		{"go1.21rc2", "bad", +1},
		{"go1.21rc", "go1.21rc", 0},
		{"go1.21rc", "go1.21", +1},
		{"go1.21rc", "go1", +1},
		{"go1.21rc", "go0.0", +1},
		{"go1.21rc", "", +1},
		{"go1.21rc", "bad", +1},
		{"go1.21", "go1.21", 0},
		{"go1.21", "go1", +1},
		{"go1.21", "go0.0", +1},
		{"go1.21", "", +1},
		{"go1.21", "bad", +1},
		{"go1", "go1", 0},
		{"go1", "go0.0", +1},
		{"go1", "", +1},
		{"go1", "bad", +1},
		{"go0.0", "go0.0", 0},
		{"go0.0", "", +1},
		{"go0.0", "bad", +1},
		{"", "", 0},
		{"", "bad", 0},
		{"bad", "bad", 0},
		// Other tests.
		{"go1.20", "go1.20.0-bigcorp", 0},
		{"go1.21", "go1.21.0-bigcorp", -1},  // Starting in Go 1.21, patch missing is different from explicit .0.
		{"go1.21.0", "go1.21.0-bigcorp", 0}, // Starting in Go 1.21, patch missing is different from explicit .0.
		{"go1.19rc1", "go1.19", -1},
	} {
		got := versions.Compare(item.x, item.y)
		if got != item.want {
			t.Errorf("versions.Compare(%q, %q)=%d. expected %d", item.x, item.y, got, item.want)
		}
		reverse := versions.Compare(item.y, item.x)
		if reverse != -got {
			t.Errorf("versions.Compare(%q, %q)=%d. expected %d", item.y, item.x, reverse, -got)
		}
	}
}

func TestLang(t *testing.T) {
	for _, item := range []struct {
		x    string
		want string
	}{
		// valid
		{"go1.21rc2", "go1.21"},
		{"go1.21.2", "go1.21"},
		{"go1.21", "go1.21"},
		{"go1", "go1"},
		// invalid
		{"bad", ""},
		{"1.21", ""},
	} {
		if got := versions.Lang(item.x); got != item.want {
			t.Errorf("versions.Lang(%q)=%q. expected %q", item.x, got, item.want)
		}
	}

}

func TestKnown(t *testing.T) {
	for _, v := range [...]string{
		versions.Go1_18,
		versions.Go1_19,
		versions.Go1_20,
		versions.Go1_21,
		versions.Go1_22,
	} {
		if !versions.IsValid(v) {
			t.Errorf("Expected known version %q to be valid.", v)
		}
		if v != versions.Lang(v) {
			t.Errorf("Expected known version %q == Lang(%q).", v, versions.Lang(v))
		}
	}
}

func TestAtLeast(t *testing.T) {
	for _, item := range [...]struct {
		v, release string
		want       bool
	}{
		{versions.Future, versions.Go1_22, true},
		{versions.Go1_22, versions.Go1_22, true},
		{"go1.21", versions.Go1_22, false},
		{"invalid", versions.Go1_22, false},
	} {
		if got := versions.AtLeast(item.v, item.release); got != item.want {
			t.Errorf("AtLeast(%q, %q)=%v. wanted %v", item.v, item.release, got, item.want)
		}
	}
}

func TestBefore(t *testing.T) {
	for _, item := range [...]struct {
		v, release string
		want       bool
	}{
		{versions.Future, versions.Go1_22, false},
		{versions.Go1_22, versions.Go1_22, false},
		{"go1.21", versions.Go1_22, true},
		{"invalid", versions.Go1_22, true}, // invalid < Go1_22
	} {
		if got := versions.Before(item.v, item.release); got != item.want {
			t.Errorf("Before(%q, %q)=%v. wanted %v", item.v, item.release, got, item.want)
		}
	}
}

func TestFileVersions(t *testing.T) {
	const source = `
	package P
	`
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, "hello.go", source, 0)
	if err != nil {
		t.Fatal(err)
	}

	for _, conf := range []types.Config{
		{GoVersion: versions.Go1_22},
		{}, // GoVersion is unset.
	} {
		info := &types.Info{
			FileVersions: make(map[*ast.File]string),
		}

		_, err = conf.Check("P", fset, []*ast.File{f}, info)
		if err != nil {
			t.Fatal(err)
		}

		v := versions.FileVersion(info, f)
		if !versions.AtLeast(v, versions.Go1_22) {
			t.Errorf("versions.AtLeast(%q, %q) expected to hold", v, versions.Go1_22)
		}

		if versions.Before(v, versions.Go1_22) {
			t.Errorf("versions.AtLeast(%q, %q) expected to be false", v, versions.Go1_22)
		}

		if conf.GoVersion == "" && v != versions.Future {
			t.Error("Expected the FileVersion to be the Future when conf.GoVersion is unset")
		}
	}
}
