package sq

import (
	"errors"
	"fmt"
	"gogs.mikescher.com/BlackForestBytes/goext/langext"
	"time"
)

//TODO UNFINISHED
// this is not finished
// idea was that we can register converter in the database struct
// they get inherited from the transactions
// and when marshallingunmarshaling (sq.Query | sq.QueryAll)
// or marshaling (sq.InsertSingle)
// the types get converter automatically...

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 ConverterOptTimeToUnixMillis = NewDBTypeConverter[*time.Time, *int64](func(v *time.Time) (*int64, error) {
	if v == nil {
		return nil, nil
	}
	return langext.Ptr(v.UnixMilli()), nil
}, func(v *int64) (*time.Time, error) {
	if v == nil {
		return nil, nil
	}
	return langext.Ptr(time.UnixMilli(*v)), 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,
	}
}
