package wrapper

import (
	"bytes"

	"braces.dev/errtrace"
	"github.com/gin-gonic/gin"
	appconfig "github.com/tidbcloud/dataflow-service/app/config"
	"github.com/tidbcloud/dataflow-service/pkg/log"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/genproto/googleapis/rpc/errdetails"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/encoding/protojson"
)

type WriterWrapper struct {
	gin.ResponseWriter
	Body *bytes.Buffer
}

func (w *WriterWrapper) Write(b []byte) (int, error) {
	// double write
	return errtrace.Wrap2(w.Body.Write(b))
}

func (w *WriterWrapper) WriteString(s string) (int, error) {
	// double write
	return errtrace.Wrap2(w.Body.WriteString(s))
}

func WriterWrapperHandler(ctx *gin.Context) {
	w := &WriterWrapper{
		Body:           &bytes.Buffer{},
		ResponseWriter: ctx.Writer,
	}
	ctx.Writer = w

	ctx.Next()

	// Write the response to gin response writer
	// if the gin response writer is empty
	_, _ = w.ResponseWriter.Write(w.Body.Bytes())
}

func NewAPIError(ctx *gin.Context, code int, err error) {
	s := status.New(codes.Code(code), err.Error())
	s, _ = s.WithDetails(&errdetails.ErrorInfo{
		Reason:   appconfig.HttpStatus2ErrReason(code),
		Metadata: map[string]string{},
	})

	// add trace id
	s, _ = s.WithDetails(&errdetails.RequestInfo{
		RequestId: log.GetLogID(ctx.Request.Context()),
	})

	ctx.Writer.Header().Set("Content-Type", "application/json")
	pb := s.Proto()
	// marshalOptions is a configurable JSON format marshaller.
	marshalOptions := protojson.MarshalOptions{
		// UseProtoNames:   true,
		EmitUnpopulated: true,
	}
	buf, err := marshalOptions.Marshal(pb)
	if err != nil {
		tilog.Warn(ctx.Request.Context(), "failed to marshal grpc status error", zap.Error(err))
		if _, err := ctx.Writer.WriteString(appconfig.Fallback); err != nil {
			tilog.Warn(ctx.Request.Context(), "failed to write fallback response", zap.Error(err))
		}
		return
	}
	if _, err := ctx.Writer.Write(buf); err != nil {
		tilog.Warn(ctx.Request.Context(), "failed to write error status response", zap.Error(err))
	}
	ctx.AbortWithStatus(code)
}
