package httptt

import (
	"encoding/json"
	"fmt"
	"math"
	"net/url"
	"reflect"
	"strings"

	"kpt-tmr-group/pkg/xerr"

	"github.com/golang/protobuf/proto"
)

type wkt interface {
	XXX_WellKnownType() string
}

var wktType = reflect.TypeOf((*wkt)(nil)).Elem()

func MarshalQueryPB(pb proto.Message) *url.Values {
	fields := make(url.Values)
	target := reflect.ValueOf(pb).Elem()
	props := proto.GetProperties(target.Type())
	for i := 0; i < target.NumField(); i++ {
		ft := target.Type().Field(i)

		if strings.HasPrefix(ft.Name, "XXX_") || ft.Anonymous {
			continue
		}
		// unexported field
		if strings.ToUpper(ft.Name[:1]) != ft.Name[:1] {
			continue
		}

		if err := setFields(&fields, target.Field(i), props.Prop[i]); err != nil {
			panic(err)
		}
	}
	return &fields
}

func setFields(fields *url.Values, target reflect.Value, prop *proto.Properties) error {
	if target.Kind() == reflect.Slice && target.Type().Elem().Kind() != reflect.Uint8 {
		for i := 0; i < target.Len(); i++ {
			if err := setFields(fields, target.Index(i), prop); err != nil {
				return xerr.WithStack(err)
			}
		}
		return nil
	}
	// Handle well-known types.
	// Most are handled up in marshalObject (because 99% are messages).
	if target.Type().Implements(wktType) {
		wkt := target.Interface().(wkt)
		switch wkt.XXX_WellKnownType() {
		case "NullValue":
			appendFields(fields, prop, "null")
			return nil
		}
	}

	if prop.Enum != "" {
		appendFields(fields, prop, target.Interface().(fmt.Stringer).String())
		return nil
	}

	if target.Kind() == reflect.Float32 || target.Kind() == reflect.Float64 {
		f := target.Float()
		var sval string
		switch {
		case math.IsInf(f, 1):
			sval = `"Infinity"`
		case math.IsInf(f, -1):
			sval = `"-Infinity"`
		case math.IsNaN(f):
			sval = `"NaN"`
		}
		if sval != "" {
			appendFields(fields, prop, sval)
			return nil
		}
	}

	b, err := json.Marshal(target.Interface())
	if err != nil {
		return xerr.WithStack(err)
	}
	appendFields(fields, prop, string(b))
	return nil
}

func appendFields(v *url.Values, prop *proto.Properties, value string) {
	key := prop.JSONName
	if key == "" {
		key = prop.OrigName
	}
	v.Add(key, value)
}