// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package datastore

import (
	"errors"
	"fmt"
	"reflect"
	"time"
	"unicode/utf8"

	"cloud.google.com/go/civil"
	pb "cloud.google.com/go/datastore/apiv1/datastorepb"
	llpb "google.golang.org/genproto/googleapis/type/latlng"
	timepb "google.golang.org/protobuf/types/known/timestamppb"
)

type saveOpts struct {
	noIndex   bool
	flatten   bool
	omitEmpty bool
}

// saveEntity saves an EntityProto into a PropertyLoadSaver or struct pointer.
func saveEntity(key *Key, src interface{}) (*pb.Entity, error) {
	var err error
	var props []Property
	if e, ok := src.(PropertyLoadSaver); ok {
		props, err = e.Save()
	} else {
		props, err = SaveStruct(src)
	}
	if err != nil {
		return nil, err
	}
	return propertiesToProto(key, props)
}

// reflectFieldSave extracts the underlying value of v by reflection,
// and tries to extract a Property that'll be appended to props.
func reflectFieldSave(props *[]Property, p Property, name string, opts saveOpts, v reflect.Value) error {
	switch x := v.Interface().(type) {
	case *Key, time.Time, GeoPoint:
		p.Value = x
	case civil.Date:
		p.Value = x.In(time.UTC)
		*props = append(*props, p)
		return nil
	case civil.Time:
		var format string
		if x.Nanosecond == 0 {
			format = "15:04:05"
		} else {
			format = "15:04:05.000000000"
		}
		val, err := time.Parse(format, x.String())
		if err != nil {
			return fmt.Errorf("datastore: error while parsing civil.Time: %w", err)
		}
		p.Value = val
		*props = append(*props, p)
		return nil
	case civil.DateTime:
		p.Value = x.In(time.UTC)
		*props = append(*props, p)
		return nil
	default:
		switch v.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			p.Value = v.Int()
		case reflect.Bool:
			p.Value = v.Bool()
		case reflect.String:
			p.Value = v.String()
		case reflect.Float32, reflect.Float64:
			p.Value = v.Float()

		case reflect.Interface:
			// Extract the interface's underlying value and then retry the save.
			// See issue https://github.com/googleapis/google-cloud-go/issues/1474.
			if v.IsNil() {
				// Nil interface becomes a nil property value (unless
				// omitEmpty is true, which is handled by the caller).
				p.Value = nil
				*props = append(*props, p)
				return nil
			}
			return reflectFieldSave(props, p, name, opts, v.Elem())

		case reflect.Slice:
			if v.Type().Elem().Kind() == reflect.Uint8 {
				p.Value = v.Bytes()
			} else {
				return saveSliceProperty(props, name, opts, v)
			}
		case reflect.Ptr:
			if isValidPointerType(v.Type().Elem()) {
				if v.IsNil() {
					// Nil pointer becomes a nil property value (unless
					// omitEmpty is true, which is handled by the caller).
					p.Value = nil
					*props = append(*props, p)
					return nil
				}
				// When we recurse on the derefenced pointer, omitempty no longer applies:
				// we already know the pointer is not empty, it doesn't matter if its referent
				// is empty or not.
				opts.omitEmpty = false
				return saveStructProperty(props, name, opts, v.Elem())
			}
			if v.Type().Elem().Kind() != reflect.Struct {
				return fmt.Errorf("datastore: unsupported struct field type: %s", v.Type())
			}
			// Pointer to struct is a special case.
			if v.IsNil() {
				return nil
			}
			v = v.Elem()
			fallthrough
		case reflect.Struct:
			if !v.CanAddr() {
				return fmt.Errorf("datastore: unsupported struct field: value is unaddressable")
			}
			vi := v.Addr().Interface()

			sub, err := newStructPLS(vi)
			if err != nil {
				return fmt.Errorf("datastore: unsupported struct field: %v", err)
			}

			if opts.flatten {
				return sub.save(props, opts, name+".")
			}

			var subProps []Property
			err = sub.save(&subProps, opts, "")
			if err != nil {
				return err
			}
			subKey, err := sub.key(v)
			if err != nil {
				return err
			}

			p.Value = &Entity{
				Key:        subKey,
				Properties: subProps,
			}
		}
	}

	if v.CanAddr() {
		vi := v.Addr().Interface()
		if pSaver, ok := vi.(PropertyLoadSaver); ok {
			val, err := pSaver.Save()
			if err != nil {
				return fmt.Errorf("field save error: %w", err)
			}
			p.Value = val
		}
	}

	if p.Value == nil {
		return fmt.Errorf("datastore: unsupported struct field type: %v", v.Type())
	}
	*props = append(*props, p)
	return nil
}

// TODO(djd): Convert this and below to return ([]Property, error).
func saveStructProperty(props *[]Property, name string, opts saveOpts, v reflect.Value) error {
	p := Property{
		Name:    name,
		NoIndex: opts.noIndex,
	}

	if opts.omitEmpty && isEmptyValue(v) {
		return nil
	}

	// First check if field type implements PLS. If so, use PLS to
	// save.
	ok, err := plsFieldSave(props, p, name, opts, v)
	if err != nil {
		return err
	}
	if ok {
		return nil
	}

	return reflectFieldSave(props, p, name, opts, v)
}

// plsFieldSave first tries to converts v's value to a PLS, then v's addressed
// value to a PLS. If neither succeeds, plsFieldSave returns false for first return
// value.
// If v is successfully converted to a PLS, plsFieldSave will then add the
// Value to property p by way of the PLS's Save method, and append it to props.
//
// If the flatten option is present in opts, name must be prepended to each property's
// name before it is appended to props. Eg. if name were "A" and a subproperty's name
// were "B", the resultant name of the property to be appended to props would be "A.B".
func plsFieldSave(props *[]Property, p Property, name string, opts saveOpts, v reflect.Value) (ok bool, err error) {
	vpls, err := plsForSave(v)
	if err != nil {
		return false, err
	}

	if vpls == nil {
		return false, nil
	}

	subProps, err := vpls.Save()
	if err != nil {
		return true, err
	}

	if opts.flatten {
		for _, subp := range subProps {
			subp.Name = name + "." + subp.Name
			*props = append(*props, subp)
		}
		return true, nil
	}

	p.Value = &Entity{Properties: subProps}
	*props = append(*props, p)

	return true, nil
}

// key extracts the *Key struct field from struct v based on the structCodec of s.
func (s structPLS) key(v reflect.Value) (*Key, error) {
	if v.Kind() != reflect.Struct {
		return nil, errors.New("datastore: cannot save key of non-struct type")
	}

	keyField := s.codec.Match(keyFieldName)

	if keyField == nil {
		return nil, nil
	}

	f := v.FieldByIndex(keyField.Index)
	k, ok := f.Interface().(*Key)
	if !ok {
		return nil, fmt.Errorf("datastore: %s field on struct %T is not a *datastore.Key", keyFieldName, v.Interface())
	}

	return k, nil
}

func saveSliceProperty(props *[]Property, name string, opts saveOpts, v reflect.Value) error {
	// Easy case: if the slice is empty, we're done.
	if v.Len() == 0 {
		return nil
	}
	// Work out the properties generated by the first element in the slice. This will
	// usually be a single property, but will be more if this is a slice of structs.
	var headProps []Property
	if err := saveStructProperty(&headProps, name, opts, v.Index(0)); err != nil {
		return err
	}

	// Convert the first element's properties into slice properties, and
	// keep track of the values in a map.
	values := make(map[string][]interface{}, len(headProps))
	for _, p := range headProps {
		values[p.Name] = append(make([]interface{}, 0, v.Len()), p.Value)
	}

	// Find the elements for the subsequent elements.
	for i := 1; i < v.Len(); i++ {
		elemProps := make([]Property, 0, len(headProps))
		if err := saveStructProperty(&elemProps, name, opts, v.Index(i)); err != nil {
			return err
		}
		for _, p := range elemProps {
			v, ok := values[p.Name]
			if !ok {
				return fmt.Errorf("datastore: unexpected property %q in elem %d of slice", p.Name, i)
			}
			values[p.Name] = append(v, p.Value)
		}
	}

	// Convert to the final properties.
	for _, p := range headProps {
		p.Value = values[p.Name]
		*props = append(*props, p)
	}
	return nil
}

func (s structPLS) Save() ([]Property, error) {
	var props []Property
	if err := s.save(&props, saveOpts{}, ""); err != nil {
		return nil, err
	}
	return props, nil
}

func (s structPLS) save(props *[]Property, opts saveOpts, prefix string) error {
	for _, f := range s.codec {
		name := prefix + f.Name
		v := getField(s.v, f.Index)
		if !v.IsValid() || !v.CanSet() {
			continue
		}

		var tagOpts saveOpts
		if f.ParsedTag != nil {
			tagOpts = f.ParsedTag.(saveOpts)
		}

		var opts1 saveOpts
		opts1.noIndex = opts.noIndex || tagOpts.noIndex
		opts1.flatten = opts.flatten || tagOpts.flatten
		opts1.omitEmpty = tagOpts.omitEmpty // don't propagate
		if err := saveStructProperty(props, name, opts1, v); err != nil {
			return err
		}
	}
	return nil
}

// getField returns the field from v at the given index path.
// If it encounters a nil-valued field in the path, getField
// stops and returns a zero-valued reflect.Value, preventing the
// panic that would have been caused by reflect's FieldByIndex.
func getField(v reflect.Value, index []int) reflect.Value {
	var zero reflect.Value
	if v.Type().Kind() != reflect.Struct {
		return zero
	}

	for _, i := range index {
		if v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Struct {
			if v.IsNil() {
				return zero
			}
			v = v.Elem()
		}
		v = v.Field(i)
	}
	return v
}

func propertiesToProto(key *Key, props []Property) (*pb.Entity, error) {
	e := &pb.Entity{
		Key:        keyToProto(key),
		Properties: map[string]*pb.Value{},
	}
	indexedProps := 0
	for _, p := range props {
		// Do not send a Key value a field to datastore.
		if p.Name == keyFieldName {
			continue
		}

		val, err := interfaceToProto(p.Value, p.NoIndex)
		if err != nil {
			return nil, fmt.Errorf("datastore: %v for a Property with Name %q", err, p.Name)
		}
		if !p.NoIndex {
			rVal := reflect.ValueOf(p.Value)
			if rVal.Kind() == reflect.Slice && rVal.Type().Elem().Kind() != reflect.Uint8 {
				indexedProps += rVal.Len()
			} else {
				indexedProps++
			}
		}
		if indexedProps > maxIndexedProperties {
			return nil, errors.New("datastore: too many indexed properties")
		}

		if _, ok := e.Properties[p.Name]; ok {
			return nil, fmt.Errorf("datastore: duplicate Property with Name %q", p.Name)
		}
		e.Properties[p.Name] = val
	}
	return e, nil
}

func interfaceToProto(iv interface{}, noIndex bool) (*pb.Value, error) {
	val := &pb.Value{ExcludeFromIndexes: noIndex}
	switch v := iv.(type) {
	case int:
		val.ValueType = &pb.Value_IntegerValue{IntegerValue: int64(v)}
	case int32:
		val.ValueType = &pb.Value_IntegerValue{IntegerValue: int64(v)}
	case int64:
		val.ValueType = &pb.Value_IntegerValue{IntegerValue: v}
	case bool:
		val.ValueType = &pb.Value_BooleanValue{BooleanValue: v}
	case string:
		if len(v) > 1500 && !noIndex {
			return nil, errors.New("string property too long to index")
		}
		if !utf8.ValidString(v) {
			return nil, fmt.Errorf("string is not valid utf8: %q", v)
		}
		val.ValueType = &pb.Value_StringValue{StringValue: v}
	case float32:
		val.ValueType = &pb.Value_DoubleValue{DoubleValue: float64(v)}
	case float64:
		val.ValueType = &pb.Value_DoubleValue{DoubleValue: v}
	case *Key:
		if v == nil {
			val.ValueType = &pb.Value_NullValue{}
		} else {
			val.ValueType = &pb.Value_KeyValue{KeyValue: keyToProto(v)}
		}
	case GeoPoint:
		if !v.Valid() {
			return nil, errors.New("invalid GeoPoint value")
		}
		val.ValueType = &pb.Value_GeoPointValue{GeoPointValue: &llpb.LatLng{
			Latitude:  v.Lat,
			Longitude: v.Lng,
		}}
	case time.Time:
		if v.Before(minTime) || v.After(maxTime) {
			return nil, errors.New("time value out of range")
		}
		val.ValueType = &pb.Value_TimestampValue{TimestampValue: &timepb.Timestamp{
			Seconds: v.Unix(),
			Nanos:   int32(v.Nanosecond()),
		}}
	case []byte:
		if len(v) > 1500 && !noIndex {
			return nil, errors.New("[]byte property too long to index")
		}
		val.ValueType = &pb.Value_BlobValue{BlobValue: v}
	case *Entity:
		e, err := propertiesToProto(v.Key, v.Properties)
		if err != nil {
			return nil, err
		}
		val.ValueType = &pb.Value_EntityValue{EntityValue: e}
	case []interface{}:
		arr := make([]*pb.Value, 0, len(v))
		for i, v := range v {
			elem, err := interfaceToProto(v, noIndex)
			if err != nil {
				return nil, fmt.Errorf("%v at index %d", err, i)
			}
			arr = append(arr, elem)
		}
		val.ValueType = &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{Values: arr}}
		// ArrayValues have ExcludeFromIndexes set on the individual items, rather
		// than the top-level value.
		val.ExcludeFromIndexes = false
	default:
		rv := reflect.ValueOf(iv)
		if !rv.IsValid() {
			val.ValueType = &pb.Value_NullValue{}
		} else if rv.Kind() == reflect.Ptr { // non-nil pointer: dereference
			if rv.IsNil() {
				val.ValueType = &pb.Value_NullValue{}
				return val, nil
			}
			return interfaceToProto(rv.Elem().Interface(), noIndex)
		} else {
			return nil, fmt.Errorf("invalid Value type %T", iv)
		}
	}
	// TODO(jbd): Support EntityValue.
	return val, nil
}

// isEmptyValue is taken from the encoding/json package in the
// standard library.
func isEmptyValue(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
		return v.Len() == 0
	case reflect.Bool:
		return !v.Bool()
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return v.Int() == 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		return v.Uint() == 0
	case reflect.Float32, reflect.Float64:
		return v.Float() == 0
	case reflect.Interface, reflect.Ptr:
		return v.IsNil()
	case reflect.Struct:
		if t, ok := v.Interface().(time.Time); ok {
			return t.IsZero()
		}
	}
	return false
}

// isValidPointerType reports whether a struct field can be a pointer to type t
// for the purposes of saving and loading.
func isValidPointerType(t reflect.Type) bool {
	if t == typeOfTime || t == typeOfGeoPoint {
		return true
	}
	switch t.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return true
	case reflect.Bool:
		return true
	case reflect.String:
		return true
	case reflect.Float32, reflect.Float64:
		return true
	}
	return false
}
