package protocal

import (
	"fmt"
	conf "nimo-shake/configure"
	"reflect"
	"strconv"
	"time"

	"github.com/aws/aws-sdk-go/service/dynamodb"
	LOG "github.com/vinllen/log4go"
	bson2 "go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
)

type TypeConverter struct {
}

// use dfs to convert to bson.M
func (tc *TypeConverter) Run(input map[string]*dynamodb.AttributeValue) (interface{}, error) {
	v := reflect.ValueOf(input)
	if output := tc.dfs(v); output == nil {
		return RawData{}, fmt.Errorf("parse failed, return nil")
	} else if out, ok := output.(RawData); !ok {
		return RawData{}, fmt.Errorf("parse failed, return type isn't RawData")
	} else if _, ok := out.Data.(bson2.M); !ok {
		return RawData{}, fmt.Errorf("parse failed, return data isn't bson.M")
	} else {
		return out, nil
	}
}

func (tc *TypeConverter) dfs(v reflect.Value) interface{} {

	funcStartT := time.Now()
	defer LOG.Debug("dfs_func kind[%v] value[%v] duration[%v]",
		v.Kind().String(), v, time.Since(funcStartT))

	switch v.Kind() {
	case reflect.Invalid:
		return nil
	case reflect.Slice, reflect.Array:
		if v.Len() == 0 {
			return nil
		}

		size := 0
		ret := make([]interface{}, 0, v.Len())
		for i := 0; i < v.Len(); i++ {
			out := tc.dfs(v.Index(i))
			md := out.(RawData)
			size += md.Size
			ret = append(ret, md.Data)
		}
		return RawData{size, ret}
	case reflect.Struct:
		if v.NumField() == 0 {
			return nil
		}
		if v.Type().Name() == "bson.Decimal128" {
			return RawData{16, v}
		}

		size := 0
		var ret interface{}
		cnt := 0
		// at most one field in AttributeValue
		for i := 0; i < v.NumField(); i++ {
			name := v.Type().Field(i).Name
			if out := tc.dfs(v.Field(i)); out != nil {
				cnt++
				if cnt > 2 {
					LOG.Crashf("illegal struct field number")
				}

				md := out.(RawData)
				size += md.Size
				md.Data = tc.convertToDetail(name, md.Data)
				ret = md.Data
			}
		}
		return RawData{size, ret}
	case reflect.Map:
		if len(v.MapKeys()) == 0 {
			return nil
		}

		size := 0
		ret := make(bson2.M)
		for _, key := range v.MapKeys() {
			name := key.String()
			name = conf.ConvertIdFunc(name)
			if out := tc.dfs(v.MapIndex(key)); out != nil {
				md := out.(RawData)
				size += md.Size
				size += len(name)
				// out = tc.convertToDetail(name, md.Data, false)
				ret[name] = md.Data
			}
		}
		return RawData{size, ret}
	case reflect.Ptr:
		if v.IsNil() {
			return nil
		} else {
			return tc.dfs(v.Elem())
		}
	case reflect.Interface:
		if v.IsNil() {
			return nil
		} else {
			return tc.dfs(v.Elem())
		}
	case reflect.String:
		out := v.String()
		return RawData{len(out), out}
	case reflect.Int:
		fallthrough
	case reflect.Int64:
		return RawData{8, v.Int()}
	case reflect.Int8:
		return RawData{1, int8(v.Int())}
	case reflect.Int16:
		return RawData{2, int16(v.Int())}
	case reflect.Int32:
		return RawData{4, int32(v.Int())}
	case reflect.Uint:
		fallthrough
	case reflect.Uint64:
		return RawData{8, v.Uint()}
	case reflect.Uint8:
		return RawData{1, uint8(v.Uint())}
	case reflect.Uint16:
		return RawData{2, uint16(v.Uint())}
	case reflect.Uint32:
		return RawData{4, uint32(v.Uint())}
	case reflect.Bool:
		// fake size
		return RawData{1, v.Bool()}
	default:
		// not support
		LOG.Error("unknown type[%v]", v.Kind())
		return nil
	}
}

func (tc *TypeConverter) convertToDetail(name string, input interface{}) interface{} {

	funcStartT := time.Now()
	defer LOG.Debug("convertToDetail_func name[%v] input[%v] duration[%v]", name, input, time.Since(funcStartT))

	switch name {
	case "B":
		list := input.([]interface{})
		output := make([]byte, 0, len(list))
		for _, ele := range list {
			output = append(output, ele.(byte))
		}
		return output
	case "BS":
		list := input.([]interface{})
		output := make([][]byte, 0, len(list))
		for _, ele := range list {
			inner := tc.convertToDetail("B", ele)
			output = append(output, inner.([]byte))
		}
		return output
	case "NS":
		list := input.([]interface{})

		var nType reflect.Type
		for _, ele := range list {
			inner := tc.convertToDetail("N", ele)
			nType = reflect.TypeOf(inner)
			break
		}

		if nType.Name() == "int" {

			output := make([]int, 0, len(list))
			for _, ele := range list {
				inner := tc.convertToDetail("N", ele)
				output = append(output, inner.(int))
			}

			return output
		} else {
			output := make([]primitive.Decimal128, 0, len(list))
			for _, ele := range list {
				inner := tc.convertToDetail("N", ele)
				output = append(output, inner.(primitive.Decimal128))
			}

			return output
		}

	case "SS":
		list := input.([]interface{})
		output := make([]string, 0, len(list))
		for _, ele := range list {
			inner := tc.convertToDetail("S", ele)
			output = append(output, inner.(string))
		}
		return output
	case "L":
		list := input.([]interface{})
		output := make([]interface{}, 0, len(list))
		for _, ele := range list {
			output = append(output, ele)
		}
		return output
	case "BOOL":
		fallthrough
	case "NULL":
		return input.(bool)
	case "N":
		v := input.(string)

		val_int, err := strconv.Atoi(v)
		if err == nil {
			return val_int
		}

		val, err := primitive.ParseDecimal128(v)
		if err != nil {
			LOG.Error("convert N to decimal128 failed[%v]", err)
			val2, err := strconv.ParseFloat(v, 64)
			if err != nil {
				LOG.Crashf("convert N to decimal128 and float64 both failed[%v]", err)
			}

			val, _ = primitive.ParseDecimal128(fmt.Sprintf("%v", val2))
			return val
		}
		return val
	case "S":
		return input.(string)
	}

	// "M"
	return input
}
