package tisentry

import (
	"context"
	"errors"

	"github.com/getsentry/sentry-go"
	grpcmiddleware "github.com/grpc-ecosystem/go-grpc-middleware"
	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/tiinf/errs"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func isInternalError(code codes.Code) bool {
	switch code {
	case codes.Unknown, codes.Aborted, codes.Unimplemented, codes.Unavailable, codes.Internal, codes.DataLoss:
		return true
	}
	return false
}

func errorHandler(err error) error {
	if err == nil {
		return nil
	}

	// compatible for bizerr
	// skip if the err is a non-exception error
	if berr, ok := err.(bizerr.BizError); ok {
		if !berr.IsException() {
			return err
		}
		if isInternalError(status.Code(err)) {
			// sentry-go does recurse to find the stack trace
			sentry.CaptureException(err)
		}
	} else if errors.Is(err, errs.TiErr) {
		if errs.IsServerError(err) {
			sentry.CaptureException(err)
		}
	}

	return err
}

// UnaryServerInterceptor returns a new unary server interceptors that sends errors to Sentry
func UnaryServerInterceptor() grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
		hub := sentry.GetHubFromContext(ctx)
		if hub == nil {
			hub = sentry.CurrentHub().Clone()
			ctx = sentry.SetHubOnContext(ctx, hub)
		}

		resp, err := handler(ctx, req)
		return resp, errorHandler(err)
	}
}

// StreamServerInterceptor returns a new streaming server interceptor that that sends errors to Sentry
func StreamServerInterceptor() grpc.StreamServerInterceptor {
	return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
		wrapped := grpcmiddleware.WrapServerStream(stream)
		hub := sentry.GetHubFromContext(wrapped.WrappedContext)
		if hub == nil {
			hub = sentry.CurrentHub().Clone()
			wrapped.WrappedContext = sentry.SetHubOnContext(wrapped.WrappedContext, hub)
		}

		err := handler(srv, wrapped)
		return errorHandler(err)
	}
}
