// Copyright 2021 CloudWeGo Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package thriftgo

import (
	"errors"
	"fmt"
	"os"

	"github.com/cloudwego/thriftgo/plugin"

	"github.com/cloudwego/kitex/tool/internal_pkg/generator"
	"github.com/cloudwego/kitex/tool/internal_pkg/util"
)

// PluginName is the link name when the kitex binary is used as a plugin for thriftgo.
const PluginName = "thrift-gen-kitex"

// TheUseOptionMessage indicates that the generating of kitex_gen is aborted due to the -use option.
const TheUseOptionMessage = "kitex_gen is not generated due to the -use option"

// Run is an entry of the plugin mode of kitex for thriftgo.
// It reads a plugin request from the standard input and writes out a response.
func Run() int {
	data, err := util.ReadInput()
	if err != nil {
		println("Failed to get input:", err.Error())
		return 1
	}

	req, err := plugin.UnmarshalRequest(data)
	if err != nil {
		println("Failed to unmarshal request:", err.Error())
		return 1
	}

	return exit(HandleRequest(req))
}

func (c *converter) failResp(err error) *plugin.Response {
	return plugin.BuildErrorResponse(err.Error(), c.Warnings...)
}

func HandleRequest(req *plugin.Request) *plugin.Response {
	var conv converter
	if err := conv.init(req); err != nil {
		return conv.failResp(err)
	}

	if err := conv.convertTypes(req); err != nil {
		return conv.failResp(err)
	}

	conv.fixImportConflicts()

	var files []*generator.File
	gen := generator.NewGenerator(&conv.Config, nil)

	conv.Package.IDLName = util.IDLName(conv.Config.IDL)

	if conv.Config.Use == "" {
		for _, s := range conv.Services {
			conv.Package.Dependencies = make(map[string]string)
			conv.Package.ServiceInfo = s
			conv.Package.Namespace = conv.svc2ast[s].GetNamespaceOrReferenceName("go")
			fs, err := gen.GenerateService(&conv.Package)
			if err != nil {
				return conv.failResp(err)
			}
			files = append(files, fs...)
		}
	}

	if conv.Config.GenerateMain {
		if len(conv.Services) == 0 {
			return conv.failResp(errors.New("no service defined in the IDL"))
		}
		if !conv.Config.IsUsingMultipleServicesTpl() {
			// if -tpl multiple_services is not set, specify the last service as the target service
			conv.Package.ServiceInfo = conv.Services[len(conv.Services)-1]
		} else {
			var svcs []*generator.ServiceInfo
			for _, svc := range conv.Services {
				if svc.GenerateHandler {
					svc.RefName = "service" + svc.ServiceName
					svcs = append(svcs, svc)
				}
			}
			conv.Package.Services = svcs
		}
		fs, err := gen.GenerateMainPackage(&conv.Package)
		if err != nil {
			return conv.failResp(err)
		}
		files = append(files, fs...)
	}

	if conv.Config.TemplateDir != "" {
		if len(conv.Services) == 0 {
			return conv.failResp(errors.New("no service defined in the IDL"))
		}
		conv.Package.ServiceInfo = conv.Services[len(conv.Services)-1]
		fs, err := gen.GenerateCustomPackage(&conv.Package)
		if err != nil {
			return conv.failResp(err)
		}
		files = append(files, fs...)
	}

	res := &plugin.Response{
		Warnings: conv.Warnings,
	}
	for _, f := range files {
		res.Contents = append(res.Contents, &plugin.Generated{
			Name:    &f.Name,
			Content: f.Content,
		})
	}

	if conv.Config.Use != "" {
		err := conv.persist(res)
		if err == nil {
			err = errors.New(TheUseOptionMessage)
		}
		return conv.failResp(err)
	}
	p := &Patcher{
		noFastAPI:             conv.Config.NoFastAPI,
		utils:                 conv.Utils,
		module:                conv.Config.ModuleName,
		copyIDL:               conv.Config.CopyIDL,
		version:               conv.Config.Version,
		record:                conv.Config.Record,
		recordCmd:             conv.Config.RecordCmd,
		deepCopyAPI:           conv.Config.DeepCopyAPI,
		protocol:              conv.Config.Protocol,
		handlerReturnKeepResp: conv.Config.HandlerReturnKeepResp,
		frugalStruct:          conv.Config.FrugalStruct,
	}
	// for cmd without setting -module
	if p.module == "" {
		p.module = conv.Config.PackagePrefix
	}
	patches, err := p.patch(req)
	if err != nil {
		return conv.failResp(fmt.Errorf("patch: %w", err))
	}
	res.Contents = append(res.Contents, patches...)

	return res
}

func exit(res *plugin.Response) int {
	data, err := plugin.MarshalResponse(res)
	if err != nil {
		println("Failed to marshal response:", err.Error())
		return 1
	}
	_, err = os.Stdout.Write(data)
	if err != nil {
		println("Error at writing response out:", err.Error())
		return 1
	}
	return 0
}
