package reflectext

import (
	"errors"
	"fmt"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"gogs.mikescher.com/BlackForestBytes/goext/langext"
	"reflect"
	"strconv"
	"strings"
	"time"
)

var primitiveSerializer = map[reflect.Type]genSerializer{

	reflect.TypeOf(""): newGenSerializer(serStringToString, serStringToString),

	reflect.TypeOf(int(0)):   newGenSerializer(serIntNumToString[int], serStringToSIntNum[int]),
	reflect.TypeOf(int32(0)): newGenSerializer(serIntNumToString[int32], serStringToSIntNum[int32]),
	reflect.TypeOf(int64(0)): newGenSerializer(serIntNumToString[int64], serStringToSIntNum[int64]),

	reflect.TypeOf(uint(0)):   newGenSerializer(serIntNumToString[uint], serStringToUIntNum[uint]),
	reflect.TypeOf(uint32(0)): newGenSerializer(serIntNumToString[uint32], serStringToUIntNum[uint32]),
	reflect.TypeOf(uint64(0)): newGenSerializer(serIntNumToString[uint64], serStringToUIntNum[uint64]),

	reflect.TypeOf(float32(0)): newGenSerializer(serFloatNumToString[float32], serStringToFloatNum[float32]),
	reflect.TypeOf(float64(0)): newGenSerializer(serFloatNumToString[float64], serStringToFloatNum[float64]),

	reflect.TypeOf(true): newGenSerializer(serBoolToString, serStringToBool),

	reflect.TypeOf(primitive.ObjectID{}): newGenSerializer(serObjectIDToString, serStringToObjectID),

	reflect.TypeOf(time.Time{}): newGenSerializer(serTimeToString, serStringToTime),
}

type genSerializer struct {
	ToString   func(v any) (string, error)
	FromString func(v string) (any, error)
}

func newGenSerializer[TData any](tostr func(v TData) (string, error), fromstr func(v string) (TData, error)) genSerializer {
	return genSerializer{
		ToString: func(v any) (string, error) {
			if tdv, ok := v.(TData); ok {
				rv, err := tostr(tdv)
				if err != nil {
					return "", err
				}
				return rv, nil
			} else {
				return "", errors.New(fmt.Sprintf("cannot convert type %T to TData (%T)", v, *new(TData)))
			}
		},
		FromString: func(v string) (any, error) {
			nv, err := fromstr(v)
			if err != nil {
				return "", err
			}
			return nv, nil
		},
	}
}

func serStringToString(v string) (string, error) {
	return v, nil
}

func serIntNumToString[TNum langext.IntegerConstraint](v TNum) (string, error) {
	return strconv.FormatInt(int64(v), 10), nil
}

func serStringToSIntNum[TNum langext.SignedConstraint](v string) (TNum, error) {
	r, err := strconv.ParseInt(v, 10, 64)
	if err != nil {
		return 0, err
	}
	return TNum(r), nil
}

func serStringToUIntNum[TNum langext.UnsignedConstraint](v string) (TNum, error) {
	r, err := strconv.ParseUint(v, 10, 64)
	if err != nil {
		return 0, err
	}
	return TNum(r), nil
}

func serFloatNumToString[TNum langext.FloatConstraint](v TNum) (string, error) {
	return strconv.FormatFloat(float64(v), 'f', -1, 64), nil
}

func serStringToFloatNum[TNum langext.FloatConstraint](v string) (TNum, error) {
	r, err := strconv.ParseFloat(v, 64)
	if err != nil {
		return 0, err
	}
	return TNum(r), nil
}

func serBoolToString(v bool) (string, error) {
	return langext.Conditional(v, "true", "false"), nil
}

func serStringToBool(v string) (bool, error) {
	if strings.ToLower(v) == "true" {
		return true, nil
	}
	if strings.ToLower(v) == "false" {
		return true, nil
	}
	return false, errors.New(fmt.Sprintf("invalid boolean value '%s'", v))
}

func serObjectIDToString(v primitive.ObjectID) (string, error) {
	return v.Hex(), nil
}

func serStringToObjectID(v string) (primitive.ObjectID, error) {
	if rv, err := primitive.ObjectIDFromHex(v); err == nil {
		return rv, nil
	} else {
		return primitive.ObjectID{}, err
	}
}

func serTimeToString(v time.Time) (string, error) {
	return v.Format(time.RFC3339Nano), nil
}

func serStringToTime(v string) (time.Time, error) {
	if rv, err := time.Parse(time.RFC3339Nano, v); err == nil {
		return rv, nil
	} else {
		return time.Time{}, err
	}
}
