package reflect

import (
	"bytes"
	"encoding/json"
	"fmt"
	"reflect"
	"slices"
	"strings"

	cnet "github.com/xtls/xray-core/common/net"
	cserial "github.com/xtls/xray-core/common/serial"
	"github.com/xtls/xray-core/infra/conf"
)

func MarshalToJson(v interface{}, insertTypeInfo bool) (string, bool) {
	if itf := marshalInterface(v, true, insertTypeInfo); itf != nil {
		if b, err := JSONMarshalWithoutEscape(itf); err == nil {
			return string(b[:]), true
		}
	}
	return "", false
}

func JSONMarshalWithoutEscape(t interface{}) ([]byte, error) {
	buffer := &bytes.Buffer{}
	encoder := json.NewEncoder(buffer)
	encoder.SetIndent("", "    ")
	encoder.SetEscapeHTML(false)
	err := encoder.Encode(t)
	return buffer.Bytes(), err
}

func marshalTypedMessage(v *cserial.TypedMessage, ignoreNullValue bool, insertTypeInfo bool) interface{} {
	tmsg, err := v.GetInstance()
	if err != nil {
		return nil
	}
	r := marshalInterface(tmsg, ignoreNullValue, insertTypeInfo)
	if msg, ok := r.(map[string]interface{}); ok && insertTypeInfo {
		msg["_TypedMessage_"] = v.Type
	}
	return r
}

func marshalSlice(v reflect.Value, ignoreNullValue bool, insertTypeInfo bool) interface{} {
	r := make([]interface{}, 0)
	for i := 0; i < v.Len(); i++ {
		rv := v.Index(i)
		if rv.CanInterface() {
			value := rv.Interface()
			r = append(r, marshalInterface(value, ignoreNullValue, insertTypeInfo))
		}
	}
	return r
}

func isNullValue(f reflect.StructField, rv reflect.Value) bool {
	if rv.Kind() == reflect.String && rv.Len() == 0 {
		return true
	} else if !isValueKind(rv.Kind()) && rv.IsNil() {
		return true
	} else if tag := f.Tag.Get("json"); strings.Contains(tag, "omitempty") {
		if !rv.IsValid() || rv.IsZero() {
			return true
		}
	}
	return false
}

func toJsonName(f reflect.StructField) string {
	if tags := f.Tag.Get("protobuf"); len(tags) > 0 {
		for _, tag := range strings.Split(tags, ",") {
			if before, after, ok := strings.Cut(tag, "="); ok && before == "json" {
				return after
			}
		}
	}
	if tag := f.Tag.Get("json"); len(tag) > 0 {
		if before, _, ok := strings.Cut(tag, ","); ok {
			return before
		} else {
			return tag
		}
	}
	return f.Name
}

func marshalStruct(v reflect.Value, ignoreNullValue bool, insertTypeInfo bool) interface{} {
	r := make(map[string]interface{})
	t := v.Type()
	for i := 0; i < v.NumField(); i++ {
		rv := v.Field(i)
		if rv.CanInterface() {
			ft := t.Field(i)
			if !ignoreNullValue || !isNullValue(ft, rv) {
				name := toJsonName(ft)
				value := rv.Interface()
				tv := marshalInterface(value, ignoreNullValue, insertTypeInfo)
				r[name] = tv
			}
		}
	}
	return r
}

func marshalMap(v reflect.Value, ignoreNullValue bool, insertTypeInfo bool) interface{} {
	// policy.level is map[uint32] *struct
	kt := v.Type().Key()
	vt := reflect.TypeOf((*interface{})(nil))
	mt := reflect.MapOf(kt, vt)
	r := reflect.MakeMap(mt)
	for _, key := range v.MapKeys() {
		rv := v.MapIndex(key)
		if rv.CanInterface() {
			iv := rv.Interface()
			tv := marshalInterface(iv, ignoreNullValue, insertTypeInfo)
			if tv != nil || !ignoreNullValue {
				r.SetMapIndex(key, reflect.ValueOf(&tv))
			}
		}
	}
	return r.Interface()
}

func marshalIString(v interface{}) (r string, ok bool) {
	defer func() {
		if err := recover(); err != nil {
			r = ""
			ok = false
		}
	}()
	if iStringFn, ok := v.(interface{ String() string }); ok {
		return iStringFn.String(), true
	}
	return "", false
}

func serializePortList(portList *cnet.PortList) (interface{}, bool) {
	if portList == nil {
		return nil, false
	}

	n := len(portList.Range)
	if n == 1 {
		if first := portList.Range[0]; first.From == first.To {
			return first.From, true
		}
	}

	r := make([]string, 0, n)
	for _, pr := range portList.Range {
		if pr.From == pr.To {
			r = append(r, pr.FromPort().String())
		} else {
			r = append(r, fmt.Sprintf("%d-%d", pr.From, pr.To))
		}
	}
	return strings.Join(r, ","), true
}

func marshalKnownType(v interface{}, ignoreNullValue bool, insertTypeInfo bool) (interface{}, bool) {
	switch ty := v.(type) {
	case cserial.TypedMessage:
		return marshalTypedMessage(&ty, ignoreNullValue, insertTypeInfo), true
	case *cserial.TypedMessage:
		return marshalTypedMessage(ty, ignoreNullValue, insertTypeInfo), true
	case map[string]json.RawMessage:
		return ty, true
	case []json.RawMessage:
		return ty, true
	case *json.RawMessage, json.RawMessage:
		return ty, true
	case *cnet.IPOrDomain:
		if domain := v.(*cnet.IPOrDomain); domain != nil {
			return domain.AsAddress().String(), true
		}
		return nil, false
	case *cnet.PortList:
		npl := v.(*cnet.PortList)
		return serializePortList(npl)
	case *conf.PortList:
		cpl := v.(*conf.PortList)
		return serializePortList(cpl.Build())
	case cnet.Address:
		if addr := v.(cnet.Address); addr != nil {
			return addr.String(), true
		}
		return nil, false
	default:
		return nil, false
	}
}

var valueKinds = []reflect.Kind{
	reflect.Bool,
	reflect.Int,
	reflect.Int8,
	reflect.Int16,
	reflect.Int32,
	reflect.Int64,
	reflect.Uint,
	reflect.Uint8,
	reflect.Uint16,
	reflect.Uint32,
	reflect.Uint64,
	reflect.Uintptr,
	reflect.Float32,
	reflect.Float64,
	reflect.Complex64,
	reflect.Complex128,
	reflect.String,
}

func isValueKind(kind reflect.Kind) bool {
	return slices.Contains(valueKinds, kind)
}

func marshalInterface(v interface{}, ignoreNullValue bool, insertTypeInfo bool) interface{} {

	if r, ok := marshalKnownType(v, ignoreNullValue, insertTypeInfo); ok {
		return r
	}

	rv := reflect.ValueOf(v)
	if rv.Kind() == reflect.Ptr {
		rv = rv.Elem()
	}
	k := rv.Kind()
	if k == reflect.Invalid {
		return nil
	}

	if ty := rv.Type().Name(); isValueKind(k) {
		if k.String() != ty {
			if s, ok := marshalIString(v); ok {
				return s
			}
		}
		return v
	}

	// fmt.Println("kind:", k, "type:", rv.Type().Name())

	switch k {
	case reflect.Struct:
		return marshalStruct(rv, ignoreNullValue, insertTypeInfo)
	case reflect.Slice:
		return marshalSlice(rv, ignoreNullValue, insertTypeInfo)
	case reflect.Array:
		return marshalSlice(rv, ignoreNullValue, insertTypeInfo)
	case reflect.Map:
		return marshalMap(rv, ignoreNullValue, insertTypeInfo)
	default:
		break
	}

	if str, ok := marshalIString(v); ok {
		return str
	}
	return nil
}