package svc

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"time"

	"golib/v1/features/mlib/mo"
	"golib/v1/network"
)

func getFormatString(value interface{}) string {
	switch v := value.(type) {
	case string:
		return v
	case []string:
		return strings.Join(v, ",")
	case bool:
		return fmt.Sprintf("%t", v)
	case []bool:
		n := make([]string, len(v))
		for i := 0; i < len(v); i++ {
			n[i] = fmt.Sprintf("%t", v[i])
		}
		return strings.Join(n, ",")
	case uint, uint8, uint16, uint32, uint64, int, int8, int16, int32, int64:
		return fmt.Sprintf("%d", v)
	case []int64:
		n := make([]string, len(v))
		for i := 0; i < len(v); i++ {
			n[i] = fmt.Sprintf("%d", v[i])
		}
		return strings.Join(n, ",")
	case float32, float64:
		return fmt.Sprintf("%.2f", v)
	case []float64:
		n := make([]string, len(v))
		for i := 0; i < len(v); i++ {
			n[i] = fmt.Sprintf("%.2f", v[i])
		}
		return strings.Join(n, ",")
	case []interface{}:
		n := make([]string, len(v))
		for i := 0; i < len(v); i++ {
			n[i] = fmt.Sprintf("%s", v[i])
		}
		return strings.Join(n, ",")
	default:
		return fmt.Sprintf("%s", v)
	}
}

func getFormatDate(value interface{}) string {
	const layout = "2006-01-02"

	switch v := value.(type) {
	case int64:
		return time.Unix(v, 0).Format(layout)
	case time.Duration:
		return time.Unix(int64(v), 0).Format(layout)
	case string:
		if _, err := time.Parse(layout, v); err == nil {
			return v
		}
		i, err := strconv.ParseInt(v, 10, 64)
		if err == nil {
			return time.Unix(i, 0).Format(layout)
		}
		return getFormatString(value)
	case time.Time:
		return v.Format(layout)
	default:
		return getFormatString(value)
	}
}

func getFormatTime(value interface{}) string {
	switch v := value.(type) {
	case int64:
		return time.Unix(v, 0).Format(mo.DateTimeLayout)
	case time.Duration:
		return time.Unix(int64(v), 0).Format(mo.DateTimeLayout)
	case string:
		if _, err := time.Parse(mo.DateTimeLayout, v); err == nil {
			return v
		}
		i, err := strconv.ParseInt(v, 10, 64)
		if err == nil {
			return time.Unix(i, 0).Format(mo.DateTimeLayout)
		}
		return getFormatString(value)
	case time.Time:
		return v.Format(mo.DateTimeLayout)
	default:
		return getFormatString(value)
	}
}

func getFormatInt64(value interface{}) int64 {
	switch v := value.(type) {
	case int64:
		return v
	default:
		i, err := strconv.ParseInt(getFormatString(value), 10, 64)
		if err != nil {
			return 65535
		}
		return i
	}
}

func getFormatFloat64(value interface{}) float64 {
	f, err := strconv.ParseFloat(fmt.Sprintf("%.2f", value), 64)
	if err != nil {
		return 65535
	}
	return f
}

var (
	ErrUnknownType = func(v interface{}) error {
		return fmt.Errorf("unknown_type: %s", reflect.TypeOf(v).Kind())
	}
)

func getBool(value interface{}) (bool, error) {
	switch v := value.(type) {
	case bool:
		return v, nil
	case string:
		return strconv.ParseBool(v)
	default:
		return false, ErrUnknownType(value)
	}
}

func getObjectId(value interface{}) (mo.ObjectID, error) {
	switch v := value.(type) {
	case mo.ObjectID:
		if v.IsZero() {
			return mo.NilObjectID, fmt.Errorf("getObjectId: %s is zero", v.String())
		}
		return v, nil
	case string:
		return mo.ObjectIDFromHex(v)
	default:
		return mo.NilObjectID, ErrUnknownType(value)
	}
}

func getBinary(value interface{}) (mo.Binary, error) {
	switch v := value.(type) {
	case mo.Binary:
		return v, nil
	case []byte:
		return mo.Binary{Subtype: mo.SubtypeGeneric, Data: v}, nil
	case string:

		if body := network.String(v).Hex(); body != nil {
			return mo.Binary{Subtype: mo.SubtypeGeneric, Data: body}, nil
		} else {
			return mo.Binary{Subtype: mo.SubtypeGeneric, Data: []byte(v)}, nil
		}
	default:
		return mo.Binary{}, ErrUnknownType(value)
	}
}

func getDate(value interface{}) (mo.DateTime, error) {
	switch v := value.(type) {
	case mo.DateTime:
		return v, nil
	case time.Duration:
		return mo.DateTime(v), nil
	case string:
		t, err := time.Parse(mo.DateTimeLayout, v)
		if err != nil {
			return 0, err
		}
		return mo.NewDateTimeFromTime(t), nil
	default:
		return 0, ErrUnknownType(value)
	}
}

func getDouble(value interface{}) (float64, error) {
	switch v := value.(type) {
	case float64:
		return v, nil
	case float32:
		return float64(v), nil
	case string:
		f, err := strconv.ParseFloat(v, 64)
		if err != nil {
			return 0, err
		}
		return f, nil
	default:
		return 0, ErrUnknownType(value)
	}
}

func getInt32(value interface{}) (int32, error) {
	switch v := value.(type) {
	case int32:
		return v, nil
	case int64:
		return int32(v), nil
	case float32:
		return int32(v), nil
	case float64:
		return int32(v), nil
	case string:
		i, err := strconv.ParseInt(v, 10, 32)
		if err != nil {
			return 0, err
		}
		return int32(i), nil
	default:
		return 0, ErrUnknownType(value)
	}
}

func getInt64(value interface{}) (int64, error) {
	switch v := value.(type) {
	case int64:
		return v, nil
	case float64:
		return int64(v), nil
	case int32:
		return int64(v), nil
	case float32:
		return int64(v), nil
	case string:
		return strconv.ParseInt(v, 10, 64)
	default:
		return 0, ErrUnknownType(value)
	}
}

func getObject(value interface{}) (mo.M, error) {
	switch v := value.(type) {
	case map[string]interface{}:
		return v, nil
	case mo.M:
		return v, nil
	case string:
		var j mo.M
		if err := mo.UnmarshalExtJSON([]byte(v), true, &j); err != nil {
			return nil, err
		}
		return j, nil
	default:
		return nil, ErrUnknownType(value)
	}
}

func getRegex(value interface{}) (mo.Regex, error) {
	switch v := value.(type) {
	case mo.Regex:
		return v, nil
	case string:
		var r mo.Regex
		if err := mo.UnmarshalExtJSON([]byte(v), true, &r); err != nil {
			return mo.Regex{}, err
		}
		return r, nil
	default:
		return mo.Regex{}, ErrUnknownType(value)
	}
}

func getJavaScript(value interface{}) (mo.JavaScript, error) {
	switch v := value.(type) {
	case mo.JavaScript:
		return v, nil
	case string:
		return mo.JavaScript(v), nil
	default:
		return "", ErrUnknownType(value)
	}
}

func getDecimal128(value interface{}) (mo.Decimal128, error) {
	switch v := value.(type) {
	case mo.Decimal128:
		return v, nil
	case string:
		s := strings.Split(v, ",")
		if len(s) != 2 {
			return mo.Decimal128{}, fmt.Errorf("getDecimal128: %s", value)
		}
		h, err := strconv.ParseUint(s[0], 10, 64)
		if err != nil {
			return mo.Decimal128{}, err
		}
		l, err := strconv.ParseUint(s[1], 10, 64)
		if err != nil {
			return mo.Decimal128{}, err
		}
		return mo.NewDecimal128(h, l), nil
	default:
		return mo.Decimal128{}, ErrUnknownType(value)
	}

}

func getArray(value interface{}) (interface{}, error) {
	if reflect.TypeOf(value).Kind() == reflect.Slice {
		return value, nil
	}
	if v, ok := value.(string); ok {
		if v == "" {
			return []interface{}{}, fmt.Errorf("value_empty")
		}
		idx := strings.Index(v, ",")
		if idx == -1 {
			return []string{v}, nil
		}
		// 格式化第一个逗号前的字符串类型
		_, t := ParseStr(v[:idx])
		switch t {
		case mo.TypeBoolean:
			old := strings.Split(v, ",")
			n := make([]bool, len(old))
			for i := 0; i < len(old); i++ {
				v, _ := ParseStr(old[i])
				n[i] = v.(bool)
			}
			return n, nil
		case mo.TypeInt64:
			old := strings.Split(v, ",")
			n := make([]int64, len(old))
			for i := 0; i < len(old); i++ {
				v, _ := ParseStr(old[i])
				n[i] = v.(int64)
			}
			return n, nil
		case mo.TypeDouble:
			old := strings.Split(v, ",")
			n := make([]float64, len(old))
			for i := 0; i < len(old); i++ {
				v, _ := ParseStr(old[i])
				n[i] = v.(float64)
			}
			return n, nil
		case mo.TypeObject:
			old := strings.Split(v, ",")
			n := make([]interface{}, len(old))
			for i := 0; i < len(old); i++ {
				v, _ := ParseStr(old[i])
				n[i] = v.(mo.M)
			}
			return n, nil
		case mo.TypeString:
			return strings.Split(v, ","), nil
		}
	}
	return nil, ErrUnknownType(value)
}

func ParseStr(v string) (interface{}, mo.Type) {
	if s, err := strconv.ParseBool(v); err == nil {
		return s, mo.TypeBoolean
	}
	if s, err := strconv.ParseInt(v, 10, 64); err == nil {
		return s, mo.TypeInt64
	}
	if s, err := strconv.ParseFloat(v, 64); err == nil {
		return s, mo.TypeDouble
	}
	var b mo.M
	if err := mo.UnmarshalExtJSON([]byte(v), true, &b); err == nil {
		return b, mo.TypeObject
	}
	return v, mo.TypeString
}
