package entity

import (
	"database/sql/driver"
	"encoding/json"
	"fmt"
	"reflect"
	"strings"
	"time"

	"gorm.io/gorm"
)

// base
type (
	// uint64 ID for all tables
	ID uint64

	// base
	Base struct {
		ID        ID        `gorm:"primary_key;not null"`
		CreatedAt time.Time `gorm:"created_at;type:TIMESTAMP;not null;<-:create"` // create and read only
	}

	// Versioned provides the versioning for model, auto increment the version when update/save
	// foo := &Foo{}
	// 1. [don't support]batch save/create: db.Save([]*Foo{foo})
	// 2. [ok]save one: db.Save(foo)
	// 3. [ok]update one field: db.Model(foo).Update("a", 1)
	// 4. [ok]update multiple fields: db.Model(foo).Updates(map[string]interface{}{"a": 1, "b": 2})
	// 5. [ok]update by model: db.Model(foo}).Select("*").Updates(foo)
	Versioned struct {
		Versioning int `gorm:"versioning; default:1"`
	}

	// Updatable table
	Updatable struct {
		Base
		UpdatedAt *time.Time `gorm:"updated_at;type:TIMESTAMP"`
	}
)

type (
	IdAndVersioning interface {
		GetID() ID
		GetVersioning() int
	}
)

func (u *Base) GetID() ID {
	return u.ID
}

func (u *Versioned) GetVersioning() int {
	return u.Versioning
}

func (u *Versioned) BeforeUpdate(tx *gorm.DB) (err error) {
	// should go here if we use db.Save(model)/db.Updates(model)
	if u.Versioning != 0 {
		tx.Statement.SetColumn("versioning", u.Versioning+1)
	} else { // should go here if we use db.Update(k,v)/db.Updates(map)
		tx.Statement.SetColumn("versioning", gorm.Expr("versioning + 1"))
	}
	return nil
}

// JSONMap
type JSONMap map[string]string

// Scan Scanner
func (t *JSONMap) Scan(value interface{}) error {
	if value == nil {
		return nil
	}

	if reflect.ValueOf(value).Len() == 0 {
		*t = map[string]string{}
		return nil
	}

	return json.Unmarshal(reflect.ValueOf(value).Bytes(), &t)
}

// Value Valuer
func (t JSONMap) Value() (driver.Value, error) {
	if t == nil {
		return "", nil
	}
	return json.Marshal(t)
}

// List
type List []string

// Scan Scanner
func (t *List) Scan(value interface{}) error {
	if value == nil {
		return nil
	}
	var b string

	// Compatible with MySQL and SQLite
	switch value := value.(type) {
	case string:
		b = value
	case []byte:
		b = string(value)
	default:
		return fmt.Errorf("list value parse failure, value: [%#v]", value)
	}

	*t = strings.Split(b, ",")

	return nil
}

// Value Valuer
func (t List) Value() (driver.Value, error) {
	if t == nil {
		return nil, nil
	}
	str := strings.Join(t, ",")
	return str, nil
}
