package sq

import (
	"encoding/json"
	"errors"
	"fmt"
	"gogs.mikescher.com/BlackForestBytes/goext/exerr"
	"gogs.mikescher.com/BlackForestBytes/goext/langext"
	"gogs.mikescher.com/BlackForestBytes/goext/rfctime"
	"reflect"
	"time"
)

type DBTypeConverter interface {
	ModelTypeString() string
	DBTypeString() string
	ModelToDB(v any) (any, error)
	DBToModel(v any) (any, error)
}

var ConverterBoolToBit = NewDBTypeConverter[bool, int](func(v bool) (int, error) {
	return langext.Conditional(v, 1, 0), nil
}, func(v int) (bool, error) {
	if v == 0 {
		return false, nil
	}
	if v == 1 {
		return true, nil
	}
	return false, errors.New(fmt.Sprintf("invalid valud for boolean: '%d'", v))
})

var ConverterTimeToUnixMillis = NewDBTypeConverter[time.Time, int64](func(v time.Time) (int64, error) {
	return v.UnixMilli(), nil
}, func(v int64) (time.Time, error) {
	return time.UnixMilli(v), nil
})

var ConverterRFCUnixMilliTimeToUnixMillis = NewDBTypeConverter[rfctime.UnixMilliTime, int64](func(v rfctime.UnixMilliTime) (int64, error) {
	return v.UnixMilli(), nil
}, func(v int64) (rfctime.UnixMilliTime, error) {
	return rfctime.NewUnixMilli(time.UnixMilli(v)), nil
})

var ConverterRFCUnixNanoTimeToUnixNanos = NewDBTypeConverter[rfctime.UnixNanoTime, int64](func(v rfctime.UnixNanoTime) (int64, error) {
	return v.UnixNano(), nil
}, func(v int64) (rfctime.UnixNanoTime, error) {
	return rfctime.NewUnixNano(time.Unix(0, v)), nil
})

var ConverterRFCUnixTimeToUnixSeconds = NewDBTypeConverter[rfctime.UnixTime, int64](func(v rfctime.UnixTime) (int64, error) {
	return v.Unix(), nil
}, func(v int64) (rfctime.UnixTime, error) {
	return rfctime.NewUnix(time.Unix(v, 0)), nil
})

// ConverterRFC339TimeToString
// Does not really use RFC339 - but sqlite does not understand timezones and the `T` delimiter
var ConverterRFC339TimeToString = NewDBTypeConverter[rfctime.RFC3339Time, string](func(v rfctime.RFC3339Time) (string, error) {
	return v.Time().In(time.UTC).Format("2006-01-02 15:04:05"), nil
}, func(v string) (rfctime.RFC3339Time, error) {
	t, err := time.Parse("2006-01-02 15:04:05", v)
	if err != nil {
		return rfctime.RFC3339Time{}, err
	}
	return rfctime.NewRFC3339(t), nil
})

// ConverterRFC339NanoTimeToString
// Does not really use RFC339 - but sqlite does not understand timezones and the `T` delimiter
var ConverterRFC339NanoTimeToString = NewDBTypeConverter[rfctime.RFC3339NanoTime, string](func(v rfctime.RFC3339NanoTime) (string, error) {
	return v.Time().In(time.UTC).Format("2006-01-02 15:04:05.999999999"), nil
}, func(v string) (rfctime.RFC3339NanoTime, error) {
	t, err := time.ParseInLocation("2006-01-02 15:04:05.999999999", v, time.UTC)
	if err != nil {
		return rfctime.RFC3339NanoTime{}, err
	}
	return rfctime.NewRFC3339Nano(t), nil
})

var ConverterJsonObjToString = NewDBTypeConverter[JsonObj, string](func(v JsonObj) (string, error) {
	mrsh, err := json.Marshal(v)
	if err != nil {
		return "", err
	}
	return string(mrsh), nil
}, func(v string) (JsonObj, error) {
	var mrsh JsonObj
	if err := json.Unmarshal([]byte(v), &mrsh); err != nil {
		return JsonObj{}, err
	}
	return mrsh, nil
})

var ConverterJsonArrToString = NewDBTypeConverter[JsonArr, string](func(v JsonArr) (string, error) {
	mrsh, err := json.Marshal(v)
	if err != nil {
		return "", err
	}
	return string(mrsh), nil
}, func(v string) (JsonArr, error) {
	var mrsh JsonArr
	if err := json.Unmarshal([]byte(v), &mrsh); err != nil {
		return JsonArr{}, err
	}
	return mrsh, nil
})

var ConverterExErrCategoryToString = NewDBTypeConverter[exerr.ErrorCategory, string](func(v exerr.ErrorCategory) (string, error) {
	return v.Category, nil
}, func(v string) (exerr.ErrorCategory, error) {
	for _, cat := range exerr.AllCategories {
		if cat.Category == v {
			return cat, nil
		}
	}
	return exerr.CatUser, errors.New("failed to convert '" + v + "' to exerr.ErrorCategory")
})

var ConverterExErrSeverityToString = NewDBTypeConverter[exerr.ErrorSeverity, string](func(v exerr.ErrorSeverity) (string, error) {
	return v.Severity, nil
}, func(v string) (exerr.ErrorSeverity, error) {
	for _, sev := range exerr.AllSeverities {
		if sev.Severity == v {
			return sev, nil
		}
	}
	return exerr.SevErr, errors.New("failed to convert '" + v + "' to exerr.ErrorSeverity")
})

var ConverterExErrTypeToString = NewDBTypeConverter[exerr.ErrorType, string](func(v exerr.ErrorType) (string, error) {
	return v.Key, nil
}, func(v string) (exerr.ErrorType, error) {
	for _, etp := range exerr.ListRegisteredTypes() {
		if etp.Key == v {
			return etp, nil
		}
	}

	return exerr.NewType(v, nil), nil
})

type dbTypeConverterImpl[TModelData any, TDBData any] struct {
	dbTypeString    string
	modelTypeString string
	todb            func(v TModelData) (TDBData, error)
	tomodel         func(v TDBData) (TModelData, error)
}

func (t *dbTypeConverterImpl[TModelData, TDBData]) ModelTypeString() string {
	return t.modelTypeString
}

func (t *dbTypeConverterImpl[TModelData, TDBData]) DBTypeString() string {
	return t.dbTypeString
}

func (t *dbTypeConverterImpl[TModelData, TDBData]) ModelToDB(v any) (any, error) {
	if vv, ok := v.(TModelData); ok {
		return t.todb(vv)
	}
	return nil, errors.New(fmt.Sprintf("Unexpected value in DBTypeConverter, expected '%s', found '%T'", t.modelTypeString, v))
}

func (t *dbTypeConverterImpl[TModelData, TDBData]) DBToModel(v any) (any, error) {
	if vv, ok := v.(TDBData); ok {
		return t.tomodel(vv)
	}
	return nil, errors.New(fmt.Sprintf("Unexpected value in DBTypeConverter, expected '%s', found '%T'", t.dbTypeString, v))
}

func NewDBTypeConverter[TModelData any, TDBData any](todb func(v TModelData) (TDBData, error), tomodel func(v TDBData) (TModelData, error)) DBTypeConverter {
	return &dbTypeConverterImpl[TModelData, TDBData]{
		dbTypeString:    fmt.Sprintf("%T", *new(TDBData)),
		modelTypeString: fmt.Sprintf("%T", *new(TModelData)),
		todb:            todb,
		tomodel:         tomodel,
	}
}

func convertValueToDB(q Queryable, value any) (any, error) {
	modelTypeStr := fmt.Sprintf("%T", value)

	for _, conv := range q.ListConverter() {
		if conv.ModelTypeString() == modelTypeStr {
			return conv.ModelToDB(value)
		}
	}

	if value != nil && reflect.TypeOf(value).Kind() == reflect.Ptr {
		vof := reflect.ValueOf(value)
		if vof.IsNil() {
			return nil, nil
		} else {
			return convertValueToDB(q, vof.Elem().Interface())
		}
	}

	return value, nil
}

func convertValueToModel(q Queryable, value any, destinationType string) (any, error) {
	dbTypeString := fmt.Sprintf("%T", value)

	for _, conv := range q.ListConverter() {
		if conv.ModelTypeString() == destinationType && conv.DBTypeString() == dbTypeString {
			return conv.DBToModel(value)
		}
	}

	return value, nil
}