// Package main convert-server
package main

import (
	"context"
	"convert-server/domain/repository"
	"convert-server/infra/db"
	pb "convert-server/pb"
	"errors"
	"flag"
	"fmt"
	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"gitlab.galaxy-immi.com/Backend-group/go-com/consts/codes"
	"gitlab.galaxy-immi.com/Backend-group/go-com/nacos"
	"gitlab.galaxy-immi.com/Backend-group/go-com/third/log"
	"gitlab.galaxy-immi.com/Backend-group/go-com/tool"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/proto"
	"net"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"

	_ "convert-server/boot"
)

var (
	serviceName = flag.String(`service-name`, `convert-server.GRPC`, `gRPC服务名`)
	wg          = sync.WaitGroup{}

	ctx        = context.TODO()
	serviceMux = runtime.NewServeMux(
		runtime.WithIncomingHeaderMatcher(tool.DefaultHeaderMatcher),
		runtime.WithForwardResponseOption(func(ctx context.Context, w http.ResponseWriter, m proto.Message) error {
			w.Header().Set("Content-Type", "application/json;charset=utf-8")
			return nil
		}),
		runtime.WithErrorHandler(func(ctx context.Context, mux *runtime.ServeMux, m runtime.Marshaler,
			writer http.ResponseWriter, r *http.Request, err error) {
			s := status.Convert(err)
			p := s.Proto()
			log.Sugar().With(zap.String(`method`, r.Method), zap.String(`URL`, r.URL.Path),
				zap.String(`remote-addr`, r.RemoteAddr), zap.String(`host`, r.Host)).
				Errorf(
					"errorHandler, code: %d, message: %s, data: %s", p.Code, p.Message, p.String())
			_, _ = writer.Write([]byte(`{"code": 0, "msg": "` + p.Message + `", "data":null}`))
			writer.WriteHeader(codes.Success)
		}),
	)
	opts = []grpc.DialOption{
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithChainUnaryInterceptor(tool.ClientInterceptor),
	}
	conf        = nacos.GetConfig()
	projectConf = conf.ConvertServer

	localIP  string
	grpcPort = fmt.Sprintf(`:%d`, projectConf.GrpcPort)
	httpPort = fmt.Sprintf(`:%d`, projectConf.HttpPort)
)

// grpcRun
func grpcRun(gs *grpc.Server, listener net.Listener) {
	err := gs.Serve(listener)
	if err != nil {
		panic(err)
	}
}

// handleSignal
func handleSignal(ctx context.Context, srv *http.Server, g *grpc.Server, localIP string, projectConf nacos.Server) {
	sigs := make(chan os.Signal, 4)
	signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	wg.Add(1)
	go func() {
		defer wg.Done()
		for sig := range sigs {
			switch sig {
			case syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT:
				log.Info(`catching signal...`)
				nacos.UnRegisterService(localIP, projectConf.GrpcPort, *serviceName)
				err := srv.Shutdown(ctx)
				if err != nil {
					log.Error(`graceful shutdown error:` + err.Error())
				}
				g.GracefulStop()
				db.ReleaseDB()
				close(sigs)
			}
		}
	}()
}

// errorInterceptor 拦截错误
func errorInterceptor(ctx context.Context, req any,
	info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
	resp, err = handler(ctx, req)
	if err != nil {
		// errors.Wrap error will wrap by withMessage & withStack
		// wrap interface
		type wrapInterface interface {
			Unwrap() error
		}
		log.Sugar().With(zap.String(`gRPC-Method`, info.FullMethod)).Errorf("%v", err)

		if _, ok := err.(wrapInterface); ok {
			// return message only
			err = errors.New(strings.Split(err.Error(), `:`)[0])
		}
	}
	return
}

func run() error {

	flag.Parse()

	if projectConf.Name != `` {
		*serviceName = projectConf.Name
	}

	nacos.InitJaeger(*serviceName)
	grpcServer := grpc.NewServer(grpc.ChainUnaryInterceptor(
		log.ErrorInterceptor, log.ValidateInterceptor, sentinelEntry))

	pb.RegisterConvertServerServer(grpcServer, service.ConvertServiceImpl)

	listener, err := net.Listen(`tcp`, grpcPort)
	if err != nil {
		return fmt.Errorf("net.Listen: %w", err)
	}

	localIP, err = tool.GetLocalIp()
	if err != nil {
		return fmt.Errorf("tool.GetLocalIp: %w", err)
	}

	go grpcRun(grpcServer, listener)

	err = pb.RegisterConvertServerHandlerFromEndpoint(ctx, serviceMux, grpcPort, opts)
	if err != nil {
		return fmt.Errorf("pb.RegisterConvertServerHandlerFromEndpoint: %w", err)
	}

	err = tool.HandleLive(serviceMux)
	if err != nil {
		return fmt.Errorf(`handleLive: %w`, err)
	}

	nacos.RegisterService(localIP, projectConf.GrpcPort, *serviceName)

	srv := &http.Server{
		Addr:    httpPort,
		Handler: tool.HttpInterceptor(serviceMux),
	}

	handleSignal(ctx, srv, grpcServer, localIP, projectConf)

	return srv.ListenAndServe()
}

func main() {
	if err := run(); err != nil && !errors.Is(err, http.ErrServerClosed) {
		panic(err)
	}
	wg.Wait()
	log.Info(`server exit`)
}
