package gen

import (
	"errors"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strings"

	"github.com/dsymonds/gotoc/parser"

	"github.com/tal-tech/go-zero/core/lang"
	"github.com/tal-tech/go-zero/tools/goctl/rpc/execx"
	astParser "github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
	"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
)

func (g *defaultRpcGenerator) genPb() error {
	importPath, filename := filepath.Split(g.Ctx.ProtoFileSrc)
	tree, err := parser.ParseFiles([]string{filename}, []string{importPath})
	if err != nil {
		return err
	}

	if len(tree.Files) == 0 {
		return errors.New("proto ast parse failed")
	}

	file := tree.Files[0]
	if len(file.Package) == 0 {
		return errors.New("expected package, but nothing found")
	}

	targetStruct := make(map[string]lang.PlaceholderType)
	for _, item := range file.Messages {
		if len(item.Messages) > 0 {
			return fmt.Errorf(`line %v: unexpected inner message near: "%v""`, item.Messages[0].Position.Line, item.Messages[0].Name)
		}

		name := stringx.From(item.Name)
		if _, ok := targetStruct[name.Lower()]; ok {
			return fmt.Errorf("line %v: duplicate %v", item.Position.Line, name)
		}
		targetStruct[name.Lower()] = lang.Placeholder
	}

	pbPath := g.dirM[dirPb]
	protoFileName := filepath.Base(g.Ctx.ProtoFileSrc)
	err = g.protocGenGo(pbPath)
	if err != nil {
		return err
	}

	pbGo := strings.TrimSuffix(protoFileName, ".proto") + ".pb.go"
	pbFile := filepath.Join(pbPath, pbGo)
	bts, err := ioutil.ReadFile(pbFile)
	if err != nil {
		return err
	}

	aspParser := astParser.NewAstParser(bts, targetStruct, g.Ctx.Console)
	ast, err := aspParser.Parse()
	if err != nil {
		return err
	}

	if len(ast.Service) == 0 {
		return fmt.Errorf("service not found")
	}
	g.ast = ast
	return nil
}

func (g *defaultRpcGenerator) protocGenGo(target string) error {
	src := filepath.Dir(g.Ctx.ProtoFileSrc)
	sh := fmt.Sprintf(`protoc -I=%s --go_out=plugins=grpc:%s %s`, src, target, g.Ctx.ProtoFileSrc)
	stdout, err := execx.Run(sh, "")
	if err != nil {
		return err
	}

	if len(stdout) > 0 {
		g.Ctx.Info(stdout)
	}

	return nil
}
