package orm

import (
	"fmt"
	"go.mongodb.org/mongo-driver/v2/bson"
	"reflect"
	"regexp"
	"strconv"
	"strings"
)

func panik(err error) {
	if err != nil {
		panic(err)
	}
}

func nameOf(i interface{}) string {
	v := reflect.ValueOf(i)
	var n string
	switch v.Kind() {
	case reflect.Slice, reflect.Map:
		if v.Type().Elem().Kind() == reflect.Pointer {
			n = v.Type().Elem().Elem().Name()
		}
	case reflect.Pointer:
		n = nameOf(reflect.Indirect(v).Interface())
	default:
		n = v.Type().Name()
	}
	return n
}

func valueOf(i interface{}) reflect.Value {
	v := reflect.ValueOf(i)
	if v.Type().Kind() == reflect.Pointer {
		v = valueOf(reflect.Indirect(v).Interface())
	}
	return v
}

func asId(i interface{}) HasID {
	v := reflect.ValueOf(i)
	var asHasId HasID
	var ok bool
	switch v.Kind() {
	case reflect.Struct:
		asHasId, ok = v.Interface().(HasID)
		if ok {
			return asHasId
		}
		v = reflect.New(v.Type())
		v.Elem().Set(reflect.ValueOf(i))
		fallthrough
	case reflect.Pointer:
		asHasId, ok = v.Interface().(HasID)
		if ok {
			return asHasId
		} else {
			panic("value does not implemenet `HasId`!")
		}
	default:
		break
	}
	return asHasId
}

func coerceInt(input reflect.Value, dst reflect.Value) interface{} {
	if input.Type().Kind() == reflect.Pointer {
		input = input.Elem()
	}
	if dst.Type().Kind() == reflect.Pointer {
		dst = dst.Elem()
	}
	if input.Type().ConvertibleTo(dst.Type()) {
		return input.Convert(dst.Type()).Interface()
	}
	return nil
}

var arrRegex, _ = regexp.Compile(`\[(?P<index>\d+)]$`)

func getNested(field string, aValue reflect.Value) (*reflect.Type, *reflect.Value, error) {
	if strings.HasPrefix(field, ".") || strings.HasSuffix(field, ".") {
		return nil, nil, fmt.Errorf(errFmtMalformedField, field)
	}
	value := aValue
	if value.Kind() == reflect.Pointer {
		value = value.Elem()
	}
	aft := value.Type()
	dots := strings.Split(field, ".")
	if value.Kind() != reflect.Struct {
		if value.Kind() == reflect.Slice {
			st := reflect.MakeSlice(value.Type().Elem(), 0, 0)
			for i := 0; i < value.Len(); i++ {
				cur := value.Index(i)
				if len(dots) > 1 {
					_, cv, _ := getNested(strings.Join(dots[1:], "."), cur.FieldByName(dots[0]))
					reflect.Append(st, *cv)
					//return getNested(, "."), fv)
				} else {
					reflect.Append(st, cur)
				}
			}
			typ := st.Type().Elem()
			return &typ, &st, nil
		}
		if len(dots) > 1 {
			return nil, nil, ErrNotSliceOrStruct
		} else {
			return &aft, &value, nil
		}
	}
	ref := value
	if ref.Kind() == reflect.Pointer {
		ref = ref.Elem()
	}
	var fv = ref.FieldByName(arrRegex.ReplaceAllString(dots[0], ""))
	if arrRegex.FindString(dots[0]) != "" && fv.Kind() == reflect.Slice {
		matches := arrRegex.FindStringSubmatch(dots[0])
		ridx, _ := strconv.Atoi(matches[0])
		idx := ridx
		fv = fv.Index(idx)
	}

	ft, _ := ref.Type().FieldByName(arrRegex.ReplaceAllString(dots[0], ""))
	if len(dots) > 1 {
		return getNested(strings.Join(dots[1:], "."), fv)
	} else {
		return &ft.Type, &fv, nil
	}
}
func makeSettable(rval reflect.Value, value interface{}) reflect.Value {
	if !rval.CanSet() {
		nv := reflect.New(rval.Type())
		nv.Elem().Set(reflect.ValueOf(value))
		return nv
	}
	return rval
}

func incrementInterface(t interface{}) interface{} {
	switch pt := t.(type) {
	case uint:
		t = pt + 1
	case uint32:
		t = pt + 1
	case uint64:
		t = pt + 1
	case int:
		t = pt + 1
	case int32:
		t = pt + 1
	case int64:
		t = pt + 1
	case string:
		t = NextStringID()
	case bson.ObjectID:
		t = bson.NewObjectID()
	default:
		panic(ErrUnsupportedID)
	}
	return t
}

func pull(s reflect.Value, idx int, typ reflect.Type) reflect.Value {
	retI := reflect.New(reflect.SliceOf(typ))
	for i := 0; i < s.Len(); i++ {
		if i == idx {
			continue
		}
		retI.Elem().Set(reflect.Append(retI.Elem(), s.Index(i)))
	}
	return retI.Elem()
}

func checkStruct(ref reflect.Value) error {
	if ref.Kind() == reflect.Slice {
		return ErrAppendMultipleDocuments
	}
	if ref.Kind() != reflect.Struct {
		return ErrNotAStruct
	}
	return nil
}

func checkSlice(ref reflect.Value) error {
	if ref.Kind() != reflect.Slice {
		return ErrNotASlice
	}
	return nil
}

func convertSlice[In, Out any](in []In) []Out {
	out := make([]Out, 0)
	for _, i := range in {
		ii, ok := any(i).(Out)
		if ok {
			out = append(out, ii)
		}
	}
	return out
}

func normalizeSliceToDocumentSlice(in any) *DocumentSlice {
	ret := make(DocumentSlice, 0)
	val := reflect.ValueOf(in)
	if val.Kind() == reflect.Pointer {
		val = val.Elem()
	}
	if val.Kind() == reflect.Slice {
		for i := 0; i < val.Len(); i++ {
			if idoc, ok := val.Index(i).Interface().(IDocument); ok {
				ret = append(ret, idoc)
			}
		}
	}
	return &ret
}

func filterMap[k comparable, v any](input map[k]v, pred func(key k, val v) bool) map[k]v {
	ret := make(map[k]v)
	for k1, v1 := range input {
		if pred(k1, v1) {
			ret[k1] = v1
		}
	}
	return ret
}