package plugin

import (
	"flag"
	"fmt"
	"math"
	"os"
	"runtime/debug"

	"github.com/hashicorp/go-hclog"
	"github.com/hashicorp/go-plugin"

	internal "gitlab.com/gitlab-org/fleeting/fleeting/internal/plugin"
	"gitlab.com/gitlab-org/fleeting/fleeting/provider"
	"google.golang.org/grpc"
)

type Option func(*options) error

type options struct {
}

// Serve automatically initializes the provided InstanceGroup with JSON data
// coming from the Fleeting architecture in GitLab, based on `json:` tags in
// the struct definition.
func Serve(impl provider.InstanceGroup) {
	logger := hclog.New(&hclog.LoggerOptions{
		Level:      hclog.Trace,
		Output:     os.Stderr,
		JSONFormat: true,
	})

	plugin.Serve(&plugin.ServeConfig{
		HandshakeConfig: internal.Handshake,
		Logger:          logger,
		VersionedPlugins: map[int]plugin.PluginSet{
			0: {
				"instancegroup": &internal.GRPCInstanceGroupPlugin{
					Logger: logger,
					Impl:   impl,
				},
			},
		},
		GRPCServer: func(opts []grpc.ServerOption) *grpc.Server {
			opts = append(opts, grpc.MaxRecvMsgSize(math.MaxInt32))
			opts = append(opts, grpc.MaxSendMsgSize(math.MaxInt32))
			return plugin.DefaultGRPCServer(opts)
		},
	})
}

// Main can be called from plugins to setup the behaviour of a executable plugin.
func Main(impl provider.InstanceGroup, v VersionInfo, opts ...Option) {
	var options options

	if v.Name == "" {
		v.Name = flag.CommandLine.Name()
	}

	// default version info is populated by built-in info
	// this information is currently lacking (main module version is always '(devel)'),
	// but hopefully this will be improved in the future.
	info, ok := debug.ReadBuildInfo()
	if ok {
		if v.Version == "" {
			v.Version = info.Main.Version
			if v.Version == "(devel)" {
				v.Version = "dev"
			}
		}

		for _, setting := range info.Settings {
			switch setting.Key {
			case "vcs.revision":
				if v.Revision != "" {
					continue
				}

				v.Revision = setting.Value
				if len(v.Revision) > 12 {
					v.Revision = v.Revision[:12]
				}
			case "vcs.time":
				if v.BuiltAt != "" {
					continue
				}

				v.BuiltAt = setting.Value
			}
		}
	}

	for _, o := range opts {
		err := o(&options)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	}

	showVersion := flag.Bool("version", false, "Show version information and exit")
	flag.Parse()

	// support 'version' as a flag for backwards-compatibility
	if *showVersion {
		fmt.Println(v.Full())
		return
	}

	switch flag.Arg(0) {
	case "version":
		fmt.Println(v.Full())

	case "bootstrap":
		if err := bootstrap(flag.Arg(1), v); err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

	case "serve":
		fallthrough

	default:
		Serve(impl)
	}
}
