package metadata

import (
	"context"
	"encoding/json"
	"strconv"
	"strings"

	"github.com/samber/lo"
	grpcmetadata "google.golang.org/grpc/metadata"
)

func MarshalRequestMetaFromCtx(ctx context.Context) ([]byte, error) {
	meta, err := extractRequestMeta(ctx, noErrorExtractor)
	if err != nil {
		return nil, err
	}

	return json.Marshal(meta) //nolint:gosec // G117: AccessToken field name matches secret pattern
	// but this is metadata marshaling, not a credential leak
}

// AppendToOutgoingMeta appends the request metadata to the context
func AppendToOutgoingMeta(ctx context.Context, meta *RequestMeta) context.Context {
	return grpcmetadata.NewOutgoingContext(ctx, generateMdFromMeta(ctx, meta))
}

// AppendToIncomingMeta appends the request metadata to the context
func AppendToIncomingMeta(ctx context.Context, meta *RequestMeta) context.Context {
	return grpcmetadata.NewIncomingContext(ctx, generateMdFromMeta(ctx, meta))
}

func generateMdFromMeta(ctx context.Context, meta *RequestMeta) grpcmetadata.MD {
	// get md from ctx
	var md grpcmetadata.MD
	md, ok := grpcmetadata.FromIncomingContext(ctx)
	if !ok {
		md, ok = grpcmetadata.FromOutgoingContext(ctx)
		if !ok {
			md = grpcmetadata.MD{}
		}
	}

	if meta.OrgID != 0 {
		md.Set(OrgIDHeaderKey, strconv.FormatUint(meta.OrgID, 10))
	}

	if meta.DefaultProjectID != 0 {
		md.Set(DefaultProjectIDHeaderKey, strconv.FormatUint(meta.DefaultProjectID, 10))
	}

	if meta.ClusterID != 0 {
		md.Set(ClusterIDHeaderKey, strconv.FormatUint(meta.ClusterID, 10))
	}

	if meta.UserID != "" {
		md.Set(UserIDHeaderKey, meta.UserID)
	}

	if meta.Email != "" {
		md.Set(UserEmailHeaderKey, meta.Email)
	}

	if meta.ApiPublicKey != "" {
		md.Set(ApiKeyHeaderKey, meta.ApiPublicKey)
	}

	if meta.ApiPublicKeyId > 0 {
		md.Set(ApiKeyHeaderKeyId, strconv.FormatUint(meta.ApiPublicKeyId, 10))
	}

	if meta.AccessToken != "" {
		md.Set(AccessTokenKey, meta.AccessToken)
	}

	if len(meta.ValidProjectList) != 0 {
		md.Set(ProjectListHeaderKey, strings.Join(lo.FlatMap(meta.ValidProjectList, func(i uint64, _ int) []string {
			return []string{strconv.FormatUint(i, 10)}
		}), ","))
	}

	if meta.IsCheckPermission != nil {
		if *meta.IsCheckPermission {
			md.Set(IsCheckPermissionKey, "true")
		} else {
			md.Set(IsCheckPermissionKey, "false")
		}
	}

	if meta.ResourceID != "" {
		md.Set(ResourceIDKey, meta.ResourceID)
	}

	if meta.IgnoreCustomStatusID != 0 {
		md.Set(IgnoreCustomStatusIDKey, strconv.FormatUint(meta.IgnoreCustomStatusID, 10))
	}

	if meta.NoProjectRequired {
		md.Set(NoProjectRequiredKey, "true")
	}

	return md
}
