package rest

import (
	"encoding/json"
	"errors"
	"reflect"
	"strconv"
	"strings"
	"time"

	"git.nspix.com/golang/micro/helper/utils"
	lru "github.com/hashicorp/golang-lru"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"gorm.io/gorm/schema"
)

var (
	ErrInvalidModelInstance = errors.New("invalid model instance")

	timeKind    = reflect.TypeOf(time.Time{}).Kind()
	timePtrKind = reflect.TypeOf(&time.Time{}).Kind()

	schemaCache, _ = lru.New(512)

	DefaultNamespace = "default"

	schemaDB *gorm.DB
)

const (
	ScenarioCreate  = "create"
	ScenarioUpdate  = "update"
	ScenarioDelete  = "delete"
	ScenarioSearch  = "search"
	ScenarioExport  = "export"
	ScenarioList    = "list"
	ScenarioView    = "view"
	ScenarioMapping = "mapping"

	Basic    = "basic"
	Advanced = "advanced"

	MatchExactly = "exactly" //精确匹配
	MatchFuzzy   = "fuzzy"   //模糊匹配
)

type (
	Rule struct { //规则配置
		Min      int      `json:"min"`
		Max      int      `json:"max"`
		Type     string   `json:"type"`
		Unique   bool     `json:"unique"`
		Required []string `json:"required"`
		Regular  string   `json:"regular"`
	}

	FieldValue struct {
		Label string      `json:"label"`
		Value interface{} `json:"value"`
		Color string      `json:"color"`
	}

	visibleCond struct {
		Column string        `json:"column"`
		Values []interface{} `json:"values"`
	}

	liveAttribute struct { //值容器
		Enable  bool     `json:"enable"`
		Type    string   `json:"type"`
		Url     string   `json:"url"`
		Columns []string `json:"columns"`
	}

	Properties struct { //属性配置
		Match        string        `json:"match"`         //匹配模式
		PrimaryKey   bool          `json:"primary_key"`   //是否为主键
		DefaultValue string        `json:"default_value"` //默认值
		Readonly     []string      `json:"readonly"`      //只读场景
		Disable      []string      `json:"disable"`       //禁用场景
		Visible      []visibleCond `json:"visible"`       //可见条件
		Values       []FieldValue  `json:"values"`        //值
		Live         liveAttribute `json:"live"`          //延时加载配置
		Icon         string        `json:"icon"`          //显示图标
		Suffix       string        `json:"suffix"`        //追加内容
		Tooltip      string        `json:"tooltip"`       //字段提示信息
		Description  string        `json:"description"`   //字段说明信息
	}

	Schema struct { //Schema的表
		Id         uint64 `json:"id" gorm:"primary_key"`
		CreatedAt  int64  `json:"created_at" gorm:"autoCreateTime"`                        //创建时间
		UpdatedAt  int64  `json:"updated_at" gorm:"autoUpdateTime"`                        //更新时间
		Namespace  string `json:"namespace" gorm:"column:namespace;type:char(60);index"`   //域
		Module     string `json:"module" gorm:"column:module_name;type:varchar(60);index"` //模块名称
		Table      string `json:"table" gorm:"column:table_name;type:varchar(120);index"`  //表名称
		Enable     uint8  `json:"enable" gorm:"column:enable;type:int(1)"`                 //是否启用
		Tag        string `json:"tag" gorm:"column:tag;type:varchar(60)"`                  //字段归类
		Column     string `json:"column" gorm:"type:varchar(120)"`                         //字段名称
		Label      string `json:"label" gorm:"type:varchar(120)"`                          //显示名称
		Type       string `json:"type" gorm:"type:varchar(120)"`                           //字段类型
		Format     string `json:"format" gorm:"type:varchar(120)"`                         //字段格式
		Native     uint8  `json:"native" gorm:"type:int(1)"`                               //是否为原生字段
		PrimaryKey uint8  `json:"primary_key" gorm:"type:int(1)"`                          //是否为主键
		Expression string `json:"expression" gorm:"type:varchar(526)"`                     //计算规则
		Rules      string `json:"rules" gorm:"type:varchar(2048)"`                         //字段规则
		Scenarios  string `json:"scenarios" gorm:"type:varchar(120)"`                      //场景
		Properties string `json:"properties" gorm:"type:varchar(4096)"`                    //字段属性
		Position   int    `json:"position"`                                                //字段排序位置
		properties *Properties
	}

	Field struct {
		Column   string `json:"column" gorm:"type:varchar(120)"`
		Label    string `json:"label" gorm:"type:varchar(120)"`
		Type     string `json:"type" gorm:"type:varchar(120)"`
		Format   string `json:"format" gorm:"type:varchar(120)"`
		Scenario string `json:"scenario" gorm:"type:varchar(120)"`
		Rules    string `json:"rules" gorm:"type:varchar(2048)"`
		Options  string `json:"options" gorm:"type:varchar(4096)"`
	}
)

func (r *Rule) String() string {
	buf, _ := json.Marshal(r)
	return string(buf)
}

// getProperties 获取属性
func (schema *Schema) getProperties() *Properties {
	if schema.properties == nil {
		schema.properties = &Properties{}
		_ = json.Unmarshal([]byte(schema.Properties), schema.properties)
	}
	return schema.properties
}

func dataTypeOf(field *schema.Field) string {
	var dataType string
	reflectType := field.FieldType
	for reflectType.Kind() == reflect.Ptr {
		reflectType = reflectType.Elem()
	}
	dataValue := reflect.Indirect(reflect.New(reflectType))
	switch dataValue.Kind() {
	case reflect.Bool:
		dataType = "boolean"
	case reflect.Int8, reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		dataType = "integer"
	case reflect.Float32, reflect.Float64:
		dataType = "double"
	case reflect.Struct:
		if _, ok := dataValue.Interface().(time.Time); ok {
			dataType = "string"
		}
	default:
		dataType = "string"
	}
	return dataType
}

//获取字段格式
func dataFormatOf(field *schema.Field) string {
	var dataType string
	dataType = field.Tag.Get("format")
	if dataType != "" {
		return dataType
	}
	//如果有枚举值，直接设置为下拉类型
	enum := field.Tag.Get("enum")
	if enum != "" {
		return "dropdown"
	}
	reflectType := field.FieldType
	for reflectType.Kind() == reflect.Ptr {
		reflectType = reflectType.Elem()
	}
	//时间处理
	if utils.InArray(field.Name, []string{"CreatedAt", "UpdatedAt", "DeletedAt"}) {
		return "datetime"
	}
	dataValue := reflect.Indirect(reflect.New(reflectType))
	switch dataValue.Kind() {
	case timeKind, timePtrKind:
		dataType = "datetime"
	case reflect.Bool:
		dataType = "boolean"
	case reflect.Int8, reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		dataType = "integer"
	case reflect.Float32, reflect.Float64:
		dataType = "decimal"
	case reflect.Struct:
		if _, ok := dataValue.Interface().(time.Time); ok {
			dataType = "datetime"
		}
	default:
		dataType = "string"
	}
	return dataType
}

func createStatement(db *gorm.DB) *gorm.Statement {
	return &gorm.Statement{
		DB:       db,
		ConnPool: db.Statement.ConnPool,
		Context:  db.Statement.Context,
		Clauses:  map[string]clause.Clause{},
	}
}

// visibleSchemas 获取某个场景下面的字段
func visibleSchemas(namespace, modelName, tableName, scenario string) (schemas []*Schema) {
	schemas, _ = getSchemas(namespace, modelName, tableName)
	values := make([]*Schema, 0)
	for _, scm := range schemas {
		if scm.Enable != 1 {
			continue
		}
		if scm.PrimaryKey == 1 {
			values = append(values, scm)
		} else {
			if strings.Contains(scm.Scenarios, scenario) {
				values = append(values, scm)
			}
		}
	}
	return values
}

// getSchemas 获取某个模型下面所有的字段配置
func getSchemas(namespace, moduleName, tableName string) (schemas []*Schema, err error) {
	schemas = make([]*Schema, 0)
	cacheKey := namespace + ":" + tableName + "@" + moduleName
	if v, ok := schemaCache.Get(cacheKey); ok {
		return v.([]*Schema), nil
	}
	if len(namespace) == 0 {
		namespace = DefaultNamespace
	}
	if err = schemaDB.Where("`namespace`=? AND `module_name`=? AND `table_name`=?", namespace, moduleName, tableName).Order("position,id ASC").Find(&schemas).Error; err == nil {
		//修改表结构缓存
		if len(schemas) > 0 {
			schemaCache.Add(cacheKey, schemas)
		}
	}
	return
}

func getSchemasNoCache(db *gorm.DB, namespace, moduleName, tableName string) (schemas []*Schema, err error) {
	tx := db.Session(&gorm.Session{NewDB: true, SkipHooks: true})
	if len(namespace) == 0 {
		namespace = DefaultNamespace
	}
	err = tx.Where("`namespace`=? AND `module_name`=? AND `table_name`=?", namespace, moduleName, tableName).Order("position,id ASC").Find(&schemas).Error
	return
}

// invalidCache 删除表结构缓存
func invalidCache(namespace, moduleName, tableName string) {
	cacheKey := namespace + ":" + tableName + "@" + moduleName
	schemaCache.Remove(cacheKey)
}

// extraSize 提取模型定义的长度大小
func extraSize(str string) int {
	var (
		sb    strings.Builder
		aflag = -1
	)
	b := make([]byte, 0)
	b = append(b, str...)
	for _, s := range b {
		if s >= '0' && s <= '9' {
			if aflag == -1 {
				aflag = 1
			}
			sb.WriteByte(s)
		} else if aflag == 1 {
			break
		}
	}
	v, _ := strconv.Atoi(sb.String())
	return v
}

// generateFieldRule 生成数据校验规则
func generateFieldRule(field *schema.Field) string {
	r := &Rule{
		Required: []string{},
	}
	if field.GORMDataType == schema.String {
		r.Max = field.Size
	}
	if field.GORMDataType == schema.Int || field.GORMDataType == schema.Float || field.GORMDataType == schema.Uint {
		r.Max = field.Scale
	}
	if field.NotNull {
		r.Required = []string{ScenarioCreate, ScenarioUpdate}
	}
	if field.PrimaryKey {
		r.Unique = true
	}
	return r.String()
}

// generateFieldProperties 生成数据属性
func generateFieldProperties(field *schema.Field) string {
	attr := &Properties{
		Match:        MatchFuzzy,
		PrimaryKey:   field.PrimaryKey,
		DefaultValue: field.DefaultValue,
		Readonly:     []string{},
		Disable:      []string{},
		Visible:      nil,
		Values:       make([]FieldValue, 0),
		Live:         liveAttribute{},
		Description:  field.DBName,
	}
	//赋值属性
	props := field.Tag.Get("props")
	if props != "" {
		vs := strings.Split(props, ";")
		for _, str := range vs {
			kv := strings.SplitN(str, ":", 2)
			if len(kv) != 2 {
				continue
			}
			switch strings.ToLower(kv[0]) {
			case "icon":
				attr.Icon = kv[1]
			case "suffix":
				attr.Suffix = kv[1]
			case "tooltip":
				attr.Tooltip = kv[1]
			case "description":
				attr.Description = kv[1]
			case "live":
				//在线数据解析
				ss := strings.Split(kv[1], ",")
				for _, s := range ss {
					if len(s) == 0 {
						continue
					}
					attr.Live.Enable = true
					if s == "dropdown" || s == "cascader" {
						attr.Live.Type = s
					} else if s[0] == '/' || strings.HasPrefix(s, "http") {
						attr.Live.Url = s
					} else {
						if strings.IndexByte(s, '.') > -1 {
							attr.Live.Columns = strings.Split(s, ".")
						}
					}
				}
			}
		}
	}
	//赋值枚举值
	enumns := field.Tag.Get("enum")
	if enumns != "" {
		vs := strings.Split(enumns, ";")
		for _, str := range vs {
			kv := strings.SplitN(str, ":", 2)
			if len(kv) != 2 {
				continue
			}
			fv := FieldValue{Value: kv[0]}
			//颜色分隔符
			if pos := strings.IndexByte(kv[1], '#'); pos > -1 {
				fv.Label = kv[1][:pos]
				fv.Color = kv[1][pos:]
			} else {
				fv.Label = kv[1]
			}
			attr.Values = append(attr.Values, fv)
		}
	}
	if !field.Creatable {
		attr.Disable = append(attr.Disable, ScenarioCreate)
	}
	if !field.Updatable {
		attr.Disable = append(attr.Disable, ScenarioUpdate)
	}
	attr.Tooltip = field.Comment
	if buf, err := json.Marshal(attr); err == nil {
		return string(buf)
	}
	return ""
}

// generateFieldScenario 生成字段应用场景
func generateFieldScenario(field *schema.Field) string {
	var ss []string
	if v, ok := field.Tag.Lookup("scenarios"); ok {
		v = strings.TrimSpace(v)
		if v != "" {
			ss = strings.Split(v, ";")
		}
	} else {
		if field.PrimaryKey {
			ss = []string{ScenarioList, ScenarioView, ScenarioExport}
		} else if field.Name == "CreatedAt" || field.Name == "UpdatedAt" {
			ss = []string{ScenarioList}
		} else if field.Name == "DeletedAt" || field.Name == "Namespace" {
			//不添加任何显示场景
			ss = []string{}
		} else {
			//高级字段只配置一些简单的场景
			if field.Tag.Get("tag") == Advanced {
				ss = []string{ScenarioCreate, ScenarioUpdate, ScenarioView, ScenarioExport}
			} else {
				ss = []string{ScenarioSearch, ScenarioList, ScenarioCreate, ScenarioUpdate, ScenarioView, ScenarioExport}
			}
		}
	}
	if buf, err := json.Marshal(ss); err == nil {
		return string(buf)
	}
	return ""
}

// migrate 合并数据表结构
func migrateUp(namespace string, value interface{}) (err error) {
	var (
		pos            int
		ok             bool
		model          Model
		moduleName     string
		tableName      string
		stmt           *gorm.Statement
		scm            *Schema
		schemas        []*Schema
		values         []*Schema
		field          *schema.Field
		columnIsExists bool
		columnName     string
		columnLabel    string
	)
	if schemaDB == nil {
		return errors.New("call initSchema first")
	}
	if len(namespace) == 0 {
		namespace = DefaultNamespace
	}
	stmt = createStatement(schemaDB)
	if value == nil {
		return ErrInvalidModelInstance
	}
	if model, ok = value.(Model); !ok {
		return ErrInvalidModelInstance
	}
	moduleName = model.ModuleName()
	tableName = model.TableName()
	if err = stmt.Parse(value); err != nil {
		return err
	}
	if schemas, err = getSchemas(namespace, moduleName, tableName); err != nil {
		schemas = make([]*Schema, 0)
	}
	totalCount := len(stmt.Schema.Fields)
	//遍历所有字段
	for _, field = range stmt.Schema.Fields {
		columnName = field.DBName
		if columnName == "-" {
			continue
		}
		if columnName == "" {
			columnName = field.Name
		}
		columnIsExists = false
		for _, scm = range schemas {
			if scm.Column == columnName {
				columnIsExists = true
				break
			}
		}
		if columnIsExists {
			continue
		}
		columnLabel = field.Tag.Get("comment")
		if columnLabel == "" {
			columnLabel = strings.Join(utils.BreakUp(field.DBName), " ")
		}
		isPrimaryKey := uint8(0)
		if field.PrimaryKey {
			isPrimaryKey = 1
		}
		pv := pos
		//默认把创建时间和更新时间放到最后
		if field.Name == "CreatedAt" || field.Name == "UpdatedAt" || field.Name == "DeletedAt" {
			pv = totalCount
		}
		tag := field.Tag.Get("tag")
		if tag == "" {
			tag = Basic
		}
		values = append(values, &Schema{
			Namespace:  namespace,
			Module:     moduleName,
			Table:      tableName,
			Enable:     1,
			Tag:        tag,
			Column:     columnName,
			Label:      columnLabel,
			Type:       strings.ToLower(dataTypeOf(field)),
			Format:     strings.ToLower(dataFormatOf(field)),
			Native:     1,
			PrimaryKey: isPrimaryKey,
			Rules:      generateFieldRule(field),
			Scenarios:  generateFieldScenario(field),
			Properties: generateFieldProperties(field),
			Position:   pv,
		})
		pos++
	}
	if len(values) > 0 {
		//batch save
		err = schemaDB.Create(values).Error
	}
	return
}

func initSchema(db *gorm.DB) (err error) {
	schemaDB = db.Session(&gorm.Session{NewDB: true})
	err = schemaDB.AutoMigrate(&Schema{})
	return
}
