// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

package grpcx

import (
	"context"
	"fmt"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/text/gstr"
)

// UnaryLogger is the default unary interceptor for logging purpose.
func (s *GrpcServer) UnaryLogger(
	ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler,
) (interface{}, error) {
	var (
		start    = time.Now()
		res, err = handler(ctx, req)
		duration = time.Since(start)
	)
	s.handleAccessLog(ctx, err, duration, info, req, res)
	s.handleErrorLog(ctx, err, duration, info, req, res)
	return res, err
}

// handleAccessLog handles the access logging for server.
func (s *GrpcServer) handleAccessLog(
	ctx context.Context, err error, duration time.Duration, info *grpc.UnaryServerInfo, req, res interface{},
) {
	if !s.config.AccessLogEnabled {
		return
	}
	content := fmt.Sprintf(
		"%s, %.3fms, %+v, %+v",
		info.FullMethod, float64(duration)/1e6, req, res,
	)
	s.config.Logger.Stdout(s.config.LogStdout).File(s.config.AccessLogPattern).Path(s.config.LogPath).Print(ctx, content)
}

// handleErrorLog handles the error logging for server.
func (s *GrpcServer) handleErrorLog(
	ctx context.Context, err error, duration time.Duration, info *grpc.UnaryServerInfo, req, res interface{},
) {
	// It does nothing if error logging is custom disabled.
	if !s.config.ErrorLogEnabled || err == nil {
		return
	}
	var (
		code          = gerror.Code(err)
		codeDetail    = code.Detail()
		codeDetailStr string
		grpcCode      codes.Code
		grpcMessage   string
	)
	if grpcStatus, ok := status.FromError(err); ok {
		grpcCode = grpcStatus.Code()
		grpcMessage = grpcStatus.Message()
	}
	if codeDetail != nil {
		codeDetailStr = gstr.Replace(fmt.Sprintf(`%+v`, codeDetail), "\n", " ")
	}
	content := fmt.Sprintf(
		`%s, %.3fms, %d, "%s", %+v, %+v, %d, "%s", "%s"`,
		info.FullMethod, float64(duration)/1e6, grpcCode, grpcMessage,
		req, res, code.Code(), code.Message(), codeDetailStr,
	)
	if s.config.ErrorStack {
		if stack := gerror.Stack(err); stack != "" {
			content += "\nStack:\n" + stack
		} else {
			content += ", " + err.Error()
		}
	} else {
		content += ", " + err.Error()
	}
	s.config.Logger.Stack(false).
		Stdout(s.config.LogStdout).
		File(s.config.ErrorLogPattern).Path(s.config.LogPath).Error(ctx, content)
}
