package tool

import (
	"context"
	"fmt"
	"net/textproto"
	"os"
	"strings"
	"time"

	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/ext"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

type MdRw struct {
	metadata.MD
}

func (c MdRw) ForeachKey(handler func(key, val string) error) error {
	for k, vs := range c.MD {
		for _, v := range vs {
			if err := handler(k, v); err != nil {
				return err
			}
		}
	}
	return nil
}

func (c MdRw) Set(key, val string) {
	key = strings.ToLower(key)
	c.MD[key] = append(c.MD[key], val)
}

func ClientInterceptor(ctx context.Context, method string, req, reply any,
	cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
	md, ok := metadata.FromOutgoingContext(ctx)
	if !ok {
		md = metadata.New(nil)
	} else {
		md = md.Copy()
	}

	span, ctx := opentracing.StartSpanFromContext(ctx,
		"Call gRPC",
		opentracing.Tag{Key: string(ext.Component), Value: "gRPC"},
		ext.SpanKindRPCClient,
	)
	span.SetTag(`method`, method)
	span.SetTag(`peer`, cc.Target())
	span.SetTag(`canonical`, cc.CanonicalTarget())
	for k, v := range md {
		for _, vv := range v {
			span.SetTag(k, vv)
		}
	}

	defer span.Finish()

	if os.Getenv("ENVIRONMENT") != "LOCAL" {
		if _, ok := ctx.Deadline(); !ok {
			var cancel context.CancelFunc
			ctx, cancel = context.WithTimeout(ctx, time.Second*10)
			defer cancel()
		}
	}

	err := opentracing.GlobalTracer().Inject(span.Context(), opentracing.TextMap, MdRw{MD: md})
	if err != nil {
		span.SetTag(`inject_error`, err.Error())
	}

	newCtx := metadata.NewOutgoingContext(ctx, md)
	err = invoker(newCtx, method, req, reply, cc, opts...)
	if err != nil {
		span.SetTag(string(ext.Error), true)
		message := fmt.Sprintf(`调用 %s 失败: %v`, method, err)
		if s, ok := status.FromError(err); ok {
			span.SetTag(`grpc.response.message`, s.Message()).
				SetTag(`rpc.grpc.status_code`, int64(s.Code()))
			message += `: ` + s.Message()
		}
		span.LogKV(`event`, `error`, `stack`, `Runtime Exception:`+message)
	}
	return err
}

// DefaultHeaderMatcher map all http headers
func DefaultHeaderMatcher(key string) (string, bool) {
	switch key = textproto.CanonicalMIMEHeaderKey(key); {
	case IsPermanentHTTPHeader(key):
		return runtime.MetadataPrefix + key, true
	case strings.HasPrefix(key, runtime.MetadataHeaderPrefix):
		return key[len(runtime.MetadataHeaderPrefix):], true
	}
	return runtime.MetadataPrefix + key, true
}

func IsPermanentHTTPHeader(hdr string) bool {
	switch hdr {
	case
		"Accept",
		"Accept-Charset",
		"Accept-Language",
		"Accept-Ranges",
		"Authorization",
		"Cache-Control",
		"Content-Type",
		"Cookie",
		"Date",
		"Expect",
		"From",
		"Host",
		"If-Match",
		"If-Modified-Since",
		"If-None-Match",
		"If-Schedule-Tag-Match",
		"If-Unmodified-Since",
		"Max-Forwards",
		"Origin",
		"Pragma",
		"Referer",
		"User-Agent",
		"Via",
		"Warning":
		return true
	}
	return false
}

// GrpcParseHeader 解析 header
func GrpcParseHeader(ctx context.Context, key string) (val []string, err error) {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return nil, fmt.Errorf("header not found or empty")
	}
	// 获取特定的头信息
	headerKey := "grpcgateway-" + strings.ToLower(key)
	hValue := md.Get(headerKey)
	if len(hValue) == 0 {
		return nil, fmt.Errorf("header key %v not found or empty", key)
	}

	return hValue, nil
}
