package crud

import (
	"context"
	"database/sql"
	"fmt"
	"git.nspix.com/golang/rest/orm/schema"
	"gorm.io/gorm"
	"reflect"
	"strconv"
	"sync"
	"time"
)

var (
	DefaultFormat = NewFormatter()
)

type HandleFunc func(ctx context.Context, value interface{}, model interface{}, scm *schema.Schema) interface{}

type Formatter struct {
	ctx     context.Context
	locker  sync.RWMutex
	formats map[string]HandleFunc
}

func stringFormat(ctx context.Context, value interface{}, model interface{}, scm *schema.Schema) interface{} {
	return fmt.Sprint(value)
}

func integerFormat(ctx context.Context, value interface{}, model interface{}, scm *schema.Schema) interface{} {
	var (
		n int
	)
	switch value.(type) {
	case float32, float64:
		n = int(reflect.ValueOf(value).Float())
	case int, int8, int16, int32, int64:
		n = int(reflect.ValueOf(value).Int())
	case uint, uint8, uint16, uint32, uint64:
		n = int(reflect.ValueOf(value).Uint())
	case string:
		n, _ = strconv.Atoi(reflect.ValueOf(value).String())
	case []byte:
		n, _ = strconv.Atoi(string(reflect.ValueOf(value).Bytes()))
	}
	return n
}

func decimalFormat(ctx context.Context, value interface{}, model interface{}, scm *schema.Schema) interface{} {
	var (
		n float64
	)
	switch value.(type) {
	case float32, float64:
		n = float64(reflect.ValueOf(value).Float())
	case int, int8, int16, int32, int64:
		n = float64(reflect.ValueOf(value).Int())
	case uint, uint8, uint16, uint32, uint64:
		n = float64(reflect.ValueOf(value).Uint())
	case string:
		n, _ = strconv.ParseFloat(reflect.ValueOf(value).String(), 64)
	case []byte:
		n, _ = strconv.ParseFloat(string(reflect.ValueOf(value).Bytes()), 64)
	}
	return n
}

func dateFormat(ctx context.Context, value interface{}, model interface{}, scm *schema.Schema) interface{} {
	if t, ok := value.(time.Time); ok {
		return t.Format("2006-01-02")
	}
	if t, ok := value.(*sql.NullTime); ok {
		if t != nil && t.Valid {
			return t.Time.Format("2006-01-02")
		}
	}
	return ""
}

func timeFormat(ctx context.Context, value interface{}, model interface{}, scm *schema.Schema) interface{} {
	if t, ok := value.(time.Time); ok {
		return t.Format("15:04:05")
	}
	if t, ok := value.(*sql.NullTime); ok {
		if t != nil && t.Valid {
			return t.Time.Format("15:04:05")
		}
	}
	return ""
}

func datetimeFormat(ctx context.Context, value interface{}, model interface{}, scm *schema.Schema) interface{} {
	if t, ok := value.(time.Time); ok {
		return t.Format("2006-01-02 15:04:05")
	}
	if t, ok := value.(*sql.NullTime); ok {
		if t != nil && t.Valid {
			return t.Time.Format("2006-01-02 15:04:05")
		}
	}
	return ""
}

func durationFormat(ctx context.Context, value interface{}, model interface{}, scm *schema.Schema) interface{} {
	var (
		hour int
		min  int
		sec  int
	)
	n := integerFormat(ctx, value, model, scm).(int)
	hour = n / 3600
	min = (n - hour*3600) / 60
	sec = n - hour*3600 - min*60
	return fmt.Sprintf("%02d:%02d:%02d", hour, min, sec)
}

func dropdownFormat(ctx context.Context, value interface{}, model interface{}, scm *schema.Schema) interface{} {
	opts := scm.GetOptions()
	if opts != nil && opts.Items != nil {
		for _, v := range opts.Items {
			if v.Key == value {
				return v.Val
			}
		}
	}
	return value
}

func (format *Formatter) Register(f string, fun HandleFunc) {
	format.locker.Lock()
	format.formats[f] = fun
	format.locker.Unlock()
}

func (format *Formatter) Format(ft string, value interface{}, model interface{}, scm *schema.Schema) interface{} {
	format.locker.RLock()
	f, ok := format.formats[ft]
	format.locker.RUnlock()
	if ok {
		return f(format.ctx, value, model, scm)
	}
	return value
}

func (format *Formatter) formatSchema(val interface{}, schemas []*schema.Schema, stmt *gorm.Statement) interface{} {
	values := make(map[string]interface{})
	refVal := reflect.Indirect(reflect.ValueOf(val))
	for _, field := range schemas {
		f := stmt.Schema.LookUpField(field.Column)
		values[field.Column] = format.Format(field.Format, refVal.FieldByName(f.Name).Interface(), val, field)
	}
	return values
}

func (format *Formatter) formatSchemas(val interface{}, schemas []*schema.Schema, stmt *gorm.Statement) interface{} {
	reflectValue := reflect.Indirect(reflect.ValueOf(val))
	if reflectValue.Type().Kind() != reflect.Slice {
		return nil
	}
	length := reflectValue.Len()
	values := make([]interface{}, length)
	for i := 0; i < length; i++ {
		values[i] = format.formatSchema(reflectValue.Index(i).Interface(), schemas, stmt)
	}
	return values
}

func NewFormatter() *Formatter {
	f := &Formatter{
		ctx:     context.Background(),
		formats: make(map[string]HandleFunc),
	}

	f.Register("string", stringFormat)
	f.Register("integer", integerFormat)
	f.Register("decimal", decimalFormat)
	f.Register("date", dateFormat)
	f.Register("time", timeFormat)
	f.Register("datetime", datetimeFormat)
	f.Register("duration", durationFormat)
	f.Register("dropdown", dropdownFormat)
	return f
}
