package main

import (
	"bytes"
	_ "embed"
	"fmt"
	"io"
	"os"
	"text/template"

	"google.golang.org/protobuf/compiler/protogen"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/descriptorpb"
	"google.golang.org/protobuf/types/pluginpb"

	"github.com/zitadel/zitadel/internal/protoc/protoc-gen-authoption/authoption"
)

var (
	//go:embed auth_method_mapping.go.tmpl
	authTemplate []byte
)

type authMethods struct {
	GoPackageName    string
	ProtoPackageName string
	ServiceName      string
	AuthOptions      []authOption
}

type authOption struct {
	Name           string
	Permission     string
	CheckFieldName string
}

func main() {

	input, _ := io.ReadAll(os.Stdin)
	var req pluginpb.CodeGeneratorRequest
	err := proto.Unmarshal(input, &req)
	if err != nil {
		panic(err)
	}

	opts := protogen.Options{}
	plugin, err := opts.New(&req)
	if err != nil {
		panic(err)
	}
	plugin.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)

	authTemp := loadTemplate(authTemplate)

	for _, file := range plugin.Files {

		var buf bytes.Buffer

		var methods authMethods
		for _, service := range file.Services {
			methods.ServiceName = service.GoName
			methods.GoPackageName = string(file.GoPackageName)
			methods.ProtoPackageName = *file.Proto.Package
			for _, method := range service.Methods {
				if options := method.Desc.Options().(*descriptorpb.MethodOptions); options != nil {
					ext := proto.GetExtension(options, authoption.E_AuthOption).(*authoption.AuthOption)
					if ext != nil {
						methods.AuthOptions = append(methods.AuthOptions, authOption{Name: string(method.Desc.Name()), Permission: ext.Permission, CheckFieldName: ext.CheckFieldName})
					}
				}
			}
		}
		if len(methods.AuthOptions) > 0 {
			authTemp.Execute(&buf, &methods)

			filename := file.GeneratedFilenamePrefix + ".pb.authoptions.go"
			file := plugin.NewGeneratedFile(filename, ".")

			file.Write(buf.Bytes())
		}
	}

	// Generate a response from our plugin and marshall as protobuf
	stdout := plugin.Response()
	out, err := proto.Marshal(stdout)
	if err != nil {
		panic(err)
	}

	// Write the response to stdout, to be picked up by protoc
	fmt.Fprintf(os.Stdout, string(out))
}

func loadTemplate(templateData []byte) *template.Template {
	return template.Must(template.New("").
		Parse(string(templateData)))
}