package main

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

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

func TestDummygen(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "Dummygen")
}

var _ = Describe("[Generator]", func() {
	Context("with error returns", func() {
		It("generates not implemented error and imports", func() {
			src := `package noop

import "time"

type MyClientInterface interface {
	GetData(id string, when time.Time) (string, error)
	SyncStatus() error
}
`

			output := generateFromSource("noop", "MyClientInterface", src)
			exp := `// Code generated by dummygen; DO NOT EDIT.

package noop

import (
	"errors"
	"time"
)

var ErrorNotImplemented = errors.New("not implemented")

type DummyMyClientInterface struct {
}

func (d *DummyMyClientInterface) GetData(id string, when time.Time) (string, error) {
	return "", ErrorNotImplemented
}

func (d *DummyMyClientInterface) SyncStatus() error {
	return ErrorNotImplemented
}
`
			Expect(output).To(Equal(exp))
		})
	})

	Context("with zero values", func() {
		It("generates zero values for non-error returns", func() {
			src := `package noop

type Foo struct {}
type MyInt int

type MyInterface interface {
	Values() (int, *Foo, []string, map[string]int, Foo, MyInt)
}
`

			output := generateFromSource("noop", "MyInterface", src)
			exp := `// Code generated by dummygen; DO NOT EDIT.

package noop

type DummyMyInterface struct {
}

func (d *DummyMyInterface) Values() (int, *Foo, []string, map[string]int, Foo, MyInt) {
	return 0, nil, nil, nil, Foo{}, MyInt(0)
}
`
			Expect(output).To(Equal(exp))
		})

		It("generates correct zero values for alias returns", func() {
			src := `package noop

type MyAlias = string

type MyInterface interface {
	Value() MyAlias
}
`

			output := generateFromSource("noop", "MyInterface", src)
			exp := `// Code generated by dummygen; DO NOT EDIT.

package noop

type DummyMyInterface struct {
}

func (d *DummyMyInterface) Value() MyAlias {
	return MyAlias("")
}
`
			Expect(output).To(Equal(exp))
		})
	})

	Context("with multiple interfaces", func() {
		It("generates multiple dummy structs in one file", func() {
			src := `package noop

import "time"

type MyInterface interface {
	GetData(id string) (string, error)
}

type MyInterface2 interface {
	Values(when time.Time) (int, error)
}
`

			output := generateFromSourceMulti("noop", []string{"MyInterface", "MyInterface2"}, src)
			exp := `// Code generated by dummygen; DO NOT EDIT.

package noop

import (
	"errors"
	"time"
)

var ErrorNotImplemented = errors.New("not implemented")

type DummyMyInterface struct {
}

func (d *DummyMyInterface) GetData(id string) (string, error) {
	return "", ErrorNotImplemented
}

type DummyMyInterface2 struct {
}

func (d *DummyMyInterface2) Values(when time.Time) (int, error) {
	return 0, ErrorNotImplemented
}
`
			Expect(output).To(Equal(exp))
		})
	})
})

func generateFromSource(pkgName, ifaceName, src string) string {
	return generateFromSourceMulti(pkgName, []string{ifaceName}, src)
}

func generateFromSourceMulti(pkgName string, ifaceNames []string, src string) string {
	fset := token.NewFileSet()
	file, err := parser.ParseFile(fset, "input.go", src, parser.ParseComments)
	Expect(err).To(BeNil())

	info := &types.Info{
		Types: make(map[ast.Expr]types.TypeAndValue),
		Defs:  make(map[*ast.Ident]types.Object),
		Uses:  make(map[*ast.Ident]types.Object),
	}

	conf := &types.Config{Importer: importer.Default()}
	pkg, err := conf.Check(pkgName, fset, []*ast.File{file}, info)
	Expect(err).To(BeNil())

	pkgInfo := &PackageInfo{
		Package: pkg,
		Files:   []*ast.File{file},
		Fset:    fset,
	}

	interfaces := make([]InterfaceSpec, 0, len(ifaceNames))
	for _, ifaceName := range ifaceNames {
		obj := pkg.Scope().Lookup(ifaceName)
		Expect(obj).NotTo(BeNil())

		typeName, ok := obj.(*types.TypeName)
		Expect(ok).To(BeTrue())

		iface, ok := typeName.Type().Underlying().(*types.Interface)
		Expect(ok).To(BeTrue())

		iface.Complete()
		interfaces = append(interfaces, InterfaceSpec{
			Name:      ifaceName,
			Interface: iface,
		})
	}

	gen := NewGenerator(GeneratorOptions{
		PackageName: pkgName,
		Interfaces:  interfaces,
		PackageInfo: pkgInfo,
	})

	content, err := gen.Generate()
	Expect(err).To(BeNil())

	return string(content)
}
