package json

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"reflect"
	"strconv"
	"time"
)

var (
	timeType            = reflect.TypeOf(time.Time{})
	jsonMarshalerType   = reflect.TypeOf(new(json.Marshaler)).Elem()
	jsonUnmarshalerType = reflect.TypeOf(new(json.Unmarshaler)).Elem()
)

// Marshal marshals the value as JSON, using Amino-compatible JSON encoding (strings for
// 64-bit numbers, and type wrappers for registered types).
func Marshal(v any) ([]byte, error) {
	buf := new(bytes.Buffer)
	err := encode(buf, v)
	if err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

// MarshalIndent marshals the value as JSON, using the given prefix and indentation.
func MarshalIndent(v any, prefix, indent string) ([]byte, error) {
	bz, err := Marshal(v)
	if err != nil {
		return nil, err
	}
	buf := new(bytes.Buffer)
	err = json.Indent(buf, bz, prefix, indent)
	if err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

func encode(w *bytes.Buffer, v any) error {
	// Bare nil values can't be reflected, so we must handle them here.
	if v == nil {
		return writeStr(w, "null")
	}
	rv := reflect.ValueOf(v)

	// If this is a registered type, defer to interface encoder regardless of whether the input is
	// an interface or a bare value. This retains Amino's behavior, but is inconsistent with
	// behavior in structs where an interface field will get the type wrapper while a bare value
	// field will not.
	if typeRegistry.name(rv.Type()) != "" {
		return encodeReflectInterface(w, rv)
	}

	return encodeReflect(w, rv)
}

func encodeReflect(w *bytes.Buffer, rv reflect.Value) error {
	if !rv.IsValid() {
		return errors.New("invalid reflect value")
	}

	// Recursively dereference if pointer.
	for rv.Kind() == reflect.Ptr {
		if rv.IsNil() {
			return writeStr(w, "null")
		}
		rv = rv.Elem()
	}

	// Convert times to UTC.
	if rv.Type() == timeType {
		rv = reflect.ValueOf(rv.Interface().(time.Time).Round(0).UTC())
	}

	// If the value implements json.Marshaler, defer to stdlib directly. Since we've already
	// dereferenced, we try implementations with both value receiver and pointer receiver. We must
	// do this after the time normalization above, and thus after dereferencing.
	if rv.Type().Implements(jsonMarshalerType) {
		return encodeStdlib(w, rv.Interface())
	} else if rv.CanAddr() && rv.Addr().Type().Implements(jsonMarshalerType) {
		return encodeStdlib(w, rv.Addr().Interface())
	}

	switch rv.Type().Kind() {
	// Complex types must be recursively encoded.
	case reflect.Interface:
		return encodeReflectInterface(w, rv)

	case reflect.Array, reflect.Slice:
		return encodeReflectList(w, rv)

	case reflect.Map:
		return encodeReflectMap(w, rv)

	case reflect.Struct:
		return encodeReflectStruct(w, rv)

	// 64-bit integers are emitted as strings, to avoid precision problems with e.g.
	// Javascript which uses 64-bit floats (having 53-bit precision).
	case reflect.Int64, reflect.Int:
		return writeStr(w, `"`+strconv.FormatInt(rv.Int(), 10)+`"`)

	case reflect.Uint64, reflect.Uint:
		return writeStr(w, `"`+strconv.FormatUint(rv.Uint(), 10)+`"`)

	// For everything else, defer to the stdlib encoding/json encoder
	default:
		return encodeStdlib(w, rv.Interface())
	}
}

func encodeReflectList(w *bytes.Buffer, rv reflect.Value) error {
	// Emit nil slices as null.
	if rv.Kind() == reflect.Slice && rv.IsNil() {
		return writeStr(w, "null")
	}

	// Encode byte slices as base64 with the stdlib encoder.
	if rv.Type().Elem().Kind() == reflect.Uint8 {
		// Stdlib does not base64-encode byte arrays, only slices, so we copy to slice.
		if rv.Type().Kind() == reflect.Array {
			slice := reflect.MakeSlice(reflect.SliceOf(rv.Type().Elem()), rv.Len(), rv.Len())
			reflect.Copy(slice, rv)
			rv = slice
		}
		return encodeStdlib(w, rv.Interface())
	}

	// Anything else we recursively encode ourselves.
	length := rv.Len()
	if err := writeStr(w, "["); err != nil {
		return err
	}
	for i := 0; i < length; i++ {
		if err := encodeReflect(w, rv.Index(i)); err != nil {
			return err
		}
		if i < length-1 {
			if err := writeStr(w, ","); err != nil {
				return err
			}
		}
	}
	return writeStr(w, "]")
}

func encodeReflectMap(w *bytes.Buffer, rv reflect.Value) error {
	if rv.Type().Key().Kind() != reflect.String {
		return errors.New("map key must be string")
	}

	// nil maps are not emitted as nil, to retain Amino compatibility.

	if err := writeStr(w, "{"); err != nil {
		return err
	}
	writeComma := false
	for _, keyrv := range rv.MapKeys() {
		if writeComma {
			if err := writeStr(w, ","); err != nil {
				return err
			}
		}
		if err := encodeStdlib(w, keyrv.Interface()); err != nil {
			return err
		}
		if err := writeStr(w, ":"); err != nil {
			return err
		}
		if err := encodeReflect(w, rv.MapIndex(keyrv)); err != nil {
			return err
		}
		writeComma = true
	}
	return writeStr(w, "}")
}

func encodeReflectStruct(w *bytes.Buffer, rv reflect.Value) error {
	sInfo := makeStructInfo(rv.Type())
	if err := writeStr(w, "{"); err != nil {
		return err
	}
	writeComma := false
	for i, fInfo := range sInfo.fields {
		frv := rv.Field(i)
		if fInfo.hidden || (fInfo.omitEmpty && frv.IsZero()) {
			continue
		}

		if writeComma {
			if err := writeStr(w, ","); err != nil {
				return err
			}
		}
		if err := encodeStdlib(w, fInfo.jsonName); err != nil {
			return err
		}
		if err := writeStr(w, ":"); err != nil {
			return err
		}
		if err := encodeReflect(w, frv); err != nil {
			return err
		}
		writeComma = true
	}
	return writeStr(w, "}")
}

func encodeReflectInterface(w *bytes.Buffer, rv reflect.Value) error {
	// Get concrete value and dereference pointers.
	for rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface {
		if rv.IsNil() {
			return writeStr(w, "null")
		}
		rv = rv.Elem()
	}

	// Look up the name of the concrete type
	name := typeRegistry.name(rv.Type())
	if name == "" {
		return fmt.Errorf("cannot encode unregistered type %v", rv.Type())
	}

	// Write value wrapped in interface envelope
	if err := writeStr(w, fmt.Sprintf(`{"type":%q,"value":`, name)); err != nil {
		return err
	}
	if err := encodeReflect(w, rv); err != nil {
		return err
	}
	return writeStr(w, "}")
}

func encodeStdlib(w *bytes.Buffer, v any) error {
	// Stream the output of the JSON marshaling directly into the buffer.
	// The stdlib encoder will write a newline, so we must truncate it,
	// which is why we pass in a bytes.Buffer throughout, not io.Writer.
	enc := json.NewEncoder(w)
	err := enc.Encode(v)
	if err != nil {
		return err
	}
	// Remove the last byte from the buffer
	w.Truncate(w.Len() - 1)
	return err
}

func writeStr(w io.Writer, s string) error {
	_, err := w.Write([]byte(s))
	return err
}
