package extract

import (
	"bytes"
	"os"
	"path"
	"strings"
	"testing"
)

var expectedOutput = `// Code generated by 'yaegi extract guthib.com/baz'. DO NOT EDIT.

package bar

import (
	"guthib.com/baz"
	"reflect"
)

func init() {
	Symbols["guthib.com/baz/baz"] = map[string]reflect.Value{
		// function, constant and variable definitions
		"Hello": reflect.ValueOf(baz.Hello),
	}
}
`

func TestPackages(t *testing.T) {
	testCases := []struct {
		desc       string
		moduleOn   string
		wd         string
		arg        string
		importPath string
		expected   string
		contains   string
		dest       string
	}{
		{
			desc: "stdlib math pkg, using go/importer",
			dest: "math",
			arg:  "math",
			// We check this one because it shows both defects when we break it: the value
			// gets corrupted, and the type becomes token.INT
			// TODO(mpl): if the ident between key and value becomes annoying, be smarter about it.
			contains: `"MaxFloat32":             reflect.ValueOf(constant.MakeFromLiteral("340282346638528859811704183484516925440", token.FLOAT, 0)),`,
		},
		{
			desc:     "using relative path, using go.mod",
			wd:       "./testdata/1/src/guthib.com/bar",
			arg:      "../baz",
			expected: expectedOutput,
		},
		{
			desc:       "using relative path, manual import path",
			wd:         "./testdata/2/src/guthib.com/bar",
			arg:        "../baz",
			importPath: "guthib.com/baz",
			expected:   expectedOutput,
		},
		{
			desc:       "using relative path, go.mod is ignored, because manual path",
			wd:         "./testdata/3/src/guthib.com/bar",
			arg:        "../baz",
			importPath: "guthib.com/baz",
			expected:   expectedOutput,
		},
		{
			desc:     "using relative path, dep in vendor, using go.mod",
			wd:       "./testdata/4/src/guthib.com/bar",
			arg:      "./vendor/guthib.com/baz",
			expected: expectedOutput,
		},
		{
			desc:       "using relative path, dep in vendor, manual import path",
			wd:         "./testdata/5/src/guthib.com/bar",
			arg:        "./vendor/guthib.com/baz",
			importPath: "guthib.com/baz",
			expected:   expectedOutput,
		},
		{
			desc:       "using relative path, package name is not same as import path",
			wd:         "./testdata/6/src/guthib.com/bar",
			arg:        "../baz-baz",
			importPath: "guthib.com/baz",
			expected:   expectedOutput,
		},
		{
			desc:       "using relative path, interface method parameter is variadic",
			wd:         "./testdata/7/src/guthib.com/variadic",
			arg:        "../variadic",
			importPath: "guthib.com/variadic",
			expected: `
// Code generated by 'yaegi extract guthib.com/variadic'. DO NOT EDIT.

package variadic

import (
	"guthib.com/variadic"
	"reflect"
)

func init() {
	Symbols["guthib.com/variadic/variadic"] = map[string]reflect.Value{
		// type definitions
		"Variadic": reflect.ValueOf((*variadic.Variadic)(nil)),

		// interface wrapper definitions
		"_Variadic": reflect.ValueOf((*_guthib_com_variadic_Variadic)(nil)),
	}
}

// _guthib_com_variadic_Variadic is an interface wrapper for Variadic type
type _guthib_com_variadic_Variadic struct {
	IValue interface{}
	WCall  func(method string, args ...[]interface{}) (interface{}, error)
}

func (W _guthib_com_variadic_Variadic) Call(method string, args ...[]interface{}) (interface{}, error) {
	return W.WCall(method, args...)
}
`[1:],
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			cwd, err := os.Getwd()
			if err != nil {
				t.Fatal(err)
			}
			wd := test.wd
			if wd == "" {
				wd = cwd
			} else {
				if err := os.Chdir(wd); err != nil {
					t.Fatal(err)
				}
				defer func() {
					if err := os.Chdir(cwd); err != nil {
						t.Fatal(err)
					}
				}()
			}

			dest := path.Base(wd)
			if test.dest != "" {
				dest = test.dest
			}
			ext := Extractor{
				Dest: dest,
			}

			var out bytes.Buffer
			if _, err := ext.Extract(test.arg, test.importPath, &out); err != nil {
				t.Fatal(err)
			}

			if test.expected != "" {
				if out.String() != test.expected {
					t.Fatalf("\nGot:\n%q\nWant: \n%q", out.String(), test.expected)
				}
			}

			if test.contains != "" {
				if !strings.Contains(out.String(), test.contains) {
					t.Fatalf("Missing expected part: %s in %s", test.contains, out.String())
				}
			}
		})
	}
}
