package bizutils

import (
	"context"
	"fmt"
	"strings"

	"github.com/golang/protobuf/proto"
	"github.com/pingcap/errcode"
	"github.com/pingcap/errors"
	"google.golang.org/grpc/metadata"
)

func GetClientIP(ctx context.Context) (string, error) {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return "", errcode.NewInternalErr(errors.New("failed to invoke FromIncomingContext"))
	}

	rips := md.Get("X-Forwarded-For")
	if len(rips) == 0 {
		return "", errcode.NewInternalErr(errors.New("remote real addr is empty"))
	}
	return parseXForwardedFor(rips)
}

// parseXForwardedFor follow: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#syntax
func parseXForwardedFor(headers []string) (string, error) {
	if len(headers) == 0 {
		return "", fmt.Errorf("no header: X-Forwarded-For")
	}
	// there should only be one header
	header := headers[0]
	for _, each := range strings.Split(header, ",") {
		return strings.TrimSpace(each), nil
	}

	return "", nil
}

type dataflowContextKey string

const (
	mutableErrorDetailsContextKey dataflowContextKey = "Dataflow/MutableErrorDetailsContextKey"
)

type mutableErrorDetails struct {
	// ErrorDetails stores all the detail information for errors.
	// TODO: currently the value is manipulated in serial,
	// if later we need to manipulate it in parallel, change it to an atomic.Value, or add a lock.
	ErrorDetails []proto.Message
}

func getMutableErrorDetailsFromContext(ctx context.Context) *mutableErrorDetails {
	val := ctx.Value(mutableErrorDetailsContextKey)
	if val == nil {
		return nil
	}
	mDetails, ok := val.(*mutableErrorDetails)
	if !ok {
		return nil
	}
	return mDetails
}

func WithInitMutableErrorDetails(ctx context.Context) context.Context {
	return context.WithValue(ctx, mutableErrorDetailsContextKey, &mutableErrorDetails{})
}

func AppendErrorDetails(ctx context.Context, details ...proto.Message) error {
	mDetails := getMutableErrorDetailsFromContext(ctx)
	if mDetails == nil {
		return errors.New("mutable error details are not initialized in the context")
	}
	mDetails.ErrorDetails = append(mDetails.ErrorDetails, details...)
	return nil
}

func GetErrorDetailsFromContext(ctx context.Context) []proto.Message {
	mDetails := getMutableErrorDetailsFromContext(ctx)
	if mDetails == nil {
		return nil
	}
	return mDetails.ErrorDetails
}
