package pkg

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"testing"

	"github.com/traefik/yaegi/interp"
	"github.com/traefik/yaegi/stdlib"
)

func TestPackages(t *testing.T) {
	testCases := []struct {
		desc      string
		goPath    string
		expected  string
		topImport string
		evalFile  string
	}{
		{
			desc:     "vendor",
			goPath:   "./_pkg/",
			expected: "root Fromage",
		},
		{
			desc:     "sub-subpackage",
			goPath:   "./_pkg0/",
			expected: "root Fromage Cheese",
		},
		{
			desc:     "subpackage",
			goPath:   "./_pkg1/",
			expected: "root Fromage!",
		},
		{
			desc:     "multiple vendor folders",
			goPath:   "./_pkg2/",
			expected: "root Fromage Cheese!",
		},
		{
			desc:     "multiple vendor folders and subpackage in vendor",
			goPath:   "./_pkg3/",
			expected: "root Fromage Couteau Cheese!",
		},
		{
			desc:     "multiple vendor folders and multiple subpackages in vendor",
			goPath:   "./_pkg4/",
			expected: "root Fromage Cheese Vin! Couteau",
		},
		{
			desc:     "vendor flat",
			goPath:   "./_pkg5/",
			expected: "root Fromage Cheese Vin! Couteau",
		},
		{
			desc:     "fallback to GOPATH",
			goPath:   "./_pkg6/",
			expected: "root Fromage Cheese Vin! Couteau",
		},
		{
			desc:     "recursive vendor",
			goPath:   "./_pkg7/",
			expected: "root vin cheese fromage",
		},
		{
			desc:     "named subpackage",
			goPath:   "./_pkg8/",
			expected: "root Fromage!",
		},
		{
			desc:      "at the project root",
			goPath:    "./_pkg10/",
			expected:  "root Fromage",
			topImport: "github.com/foo",
		},
		{
			desc:     "eval main that has vendored dep",
			goPath:   "./_pkg11/",
			expected: "Fromage",
			evalFile: "./_pkg11/src/foo/foo.go",
		},
		{
			desc:      "vendor dir is a sibling or an uncle",
			goPath:    "./_pkg12/",
			expected:  "Yo hello",
			topImport: "guthib.com/foo/pkg",
		},
		{
			desc:     "eval main with vendor as a sibling",
			goPath:   "./_pkg12/",
			expected: "Yo hello",
			evalFile: "./_pkg12/src/guthib.com/foo/main.go",
		},
		{
			desc:     "eval main with vendor",
			goPath:   "./_pkg13/",
			expected: "foobar",
			evalFile: "./_pkg13/src/guthib.com/foo/bar/main.go",
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			goPath, err := filepath.Abs(filepath.FromSlash(test.goPath))
			if err != nil {
				t.Fatal(err)
			}

			var stdout, stderr bytes.Buffer
			i := interp.New(interp.Options{GoPath: goPath, Stdout: &stdout, Stderr: &stderr})
			// Use binary standard library
			if err := i.Use(stdlib.Symbols); err != nil {
				t.Fatal(err)
			}

			var msg string
			if test.evalFile != "" {
				if _, err := i.EvalPath(filepath.FromSlash(test.evalFile)); err != nil {
					fatalStderrf(t, "%v", err)
				}
				msg = stdout.String()
			} else {
				// Load pkg from sources
				topImport := "github.com/foo/pkg"
				if test.topImport != "" {
					topImport = test.topImport
				}
				if _, err = i.Eval(fmt.Sprintf(`import %q`, topImport)); err != nil {
					t.Fatal(err)
				}
				value, err := i.Eval(`pkg.NewSample()`)
				if err != nil {
					t.Fatal(err)
				}

				fn := value.Interface().(func() string)

				msg = fn()
			}

			if msg != test.expected {
				fatalStderrf(t, "Got %q, want %q", msg, test.expected)
			}
		})
	}
}

func fatalStderrf(t *testing.T, format string, args ...interface{}) {
	t.Helper()

	fmt.Fprintf(os.Stderr, format+"\n", args...)
	t.FailNow()
}

func TestPackagesError(t *testing.T) {
	testCases := []struct {
		desc     string
		goPath   string
		expected string
	}{
		{
			desc:     "different packages in the same directory",
			goPath:   "./_pkg9/",
			expected: `1:21: import "github.com/foo/pkg" error: found packages pkg and pkgfalse in _pkg9/src/github.com/foo/pkg`,
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			// Init go interpreter
			i := interp.New(interp.Options{GoPath: test.goPath})
			// Use binary standard library
			if err := i.Use(stdlib.Symbols); err != nil {
				t.Fatal(err)
			}

			// Load pkg from sources
			_, err := i.Eval(`import "github.com/foo/pkg"`)
			if err == nil {
				t.Fatalf("got no error, want %q", test.expected)
			}

			if err.Error() != test.expected {
				t.Errorf("got %q, want %q", err.Error(), test.expected)
			}
		})
	}
}
