package base

import (
	"encoding/json"
	"fmt"
	"reflect"
	"strconv"
	"strings"

	"git.fsdpf.net/go/contracts"
	"git.fsdpf.net/go/contracts/res_type"
	"git.fsdpf.net/go/db"
	"git.fsdpf.net/go/db/schema"
	"github.com/google/uuid"
	"github.com/spf13/cast"
)

// 资源字段
type ResField struct {
	Uuid         string                `db:"uuid"`
	Name         string                `db:"name"`
	Code         string                `db:"code"`
	CodeResource string                `db:"codeResource"`
	DataType     contracts.ResDataType `db:"table_type"`
	Length       string                `db:"length"`
	Comment      string                `db:"comment"`
	Default      string                `db:"default"`
}

func (this ResField) ToStructField(tags ...string) reflect.StructField {
	var typ reflect.Type

	fCode := this.Code
	fTag := `db:"` + fCode + `" json:"` + fCode + `"`

	if len(tags) > 0 {
		fTag = strings.Join(tags, " ") + " " + fTag
	}

	switch this.DataType {
	case contracts.ResDataType_String, contracts.ResDataType_Text, contracts.ResDataType_Enum,
		contracts.ResDataType_Timestamp, contracts.ResDataType_Date, contracts.ResDataType_Datetime:
		typ = reflect.TypeOf(res_type.ResFieldByString(""))
	case contracts.ResDataType_Integer, contracts.ResDataType_SmallInteger:
		typ = reflect.TypeOf(res_type.ResFieldByInteger(0))
	case contracts.ResDataType_Decimal:
		typ = reflect.TypeOf(float64(0))
	case contracts.ResDataType_Boolean:
		typ = reflect.TypeOf(true)
	case contracts.ResDataType_Json:
		if this.Default != "" && this.Default[0:1] == "[" {
			typ = reflect.TypeOf(res_type.ResFieldByAnys{})
		} else {
			typ = reflect.TypeOf(res_type.ResFieldByMap{})
		}
	}

	return reflect.StructField{
		Name: strings.ToUpper(fCode[:1]) + fCode[1:],
		Tag:  reflect.StructTag(fTag),
		Type: typ,
	}
}

func (this ResField) GetCode() string {
	return this.Code
}

func (this ResField) GetCodeResource() string {
	return this.CodeResource
}

func (this ResField) GetName() string {
	return this.Name
}

func (this ResField) GetDataType() contracts.ResDataType {
	return this.DataType
}

func (this ResField) GetQueryDataType() contracts.QueryDataType {
	switch this.GetDataType() {
	case contracts.ResDataType_Enum,
		contracts.ResDataType_Timestamp, contracts.ResDataType_Date,
		contracts.ResDataType_Datetime, contracts.ResDataType_String,
		contracts.ResDataType_Text:
		return contracts.QueryDataType_String
	case contracts.ResDataType_Integer, contracts.ResDataType_SmallInteger:
		return contracts.QueryDataType_Integer
	case contracts.ResDataType_Decimal:
		return contracts.QueryDataType_Float
	case contracts.ResDataType_Boolean:
		return contracts.QueryDataType_Bool
	case contracts.ResDataType_Json:
		if this.Default != "" && this.Default[0:1] == "[" {
			return contracts.QueryDataType_Array
		}
		return contracts.QueryDataType_Json
	}
	return contracts.QueryDataType_String
}

func (this ResField) ToValue(v any) any {
	// 类型转换, 填补 cast 未知的类型
	switch data := v.(type) {
	case res_type.ResFieldByInteger:
		v = int64(data)
	case res_type.ResFieldByFloat:
		v = float64(data)
	case res_type.ResFieldByNumber:
		v = float64(data)
	case res_type.ResFieldByString:
		v = strings.Trim(string(data), " ")
	case res_type.ResFieldByAnys:
		if v != nil {
			b, _ := json.Marshal(v)
			return string(b)
		}
		v = []any(data)
	case res_type.ResFieldByMap:
		if v != nil {
			b, _ := json.Marshal(v)
			return string(b)
		}
		v = map[string]any(data)
	case []any, []string, []int, []float64, []float32, []int64:
		if v != nil {
			b, _ := json.Marshal(v)
			return string(b)
		}
		v = data
	case map[string]any, map[string]string, map[string]int:
		if v != nil {
			b, _ := json.Marshal(v)
			return string(b)
		}
		v = data
	}

	switch this.DataType {
	case contracts.ResDataType_String, contracts.ResDataType_Text, contracts.ResDataType_Enum,
		contracts.ResDataType_Timestamp, contracts.ResDataType_Date, contracts.ResDataType_Datetime:
		return strings.Trim(cast.ToString(v), " ")
	case contracts.ResDataType_Integer, contracts.ResDataType_SmallInteger:
		return cast.ToInt(v)
	case contracts.ResDataType_Decimal:
		return strings.Trim(cast.ToString(v), " ")
	case contracts.ResDataType_Boolean:
		v, _ := strconv.ParseBool(fmt.Sprintf("%v", v))
		return v
	case contracts.ResDataType_Json:
		if v == nil {
			if this.Default != "" && this.Default[0:1] == "[" {
				return "[]"
			} else if this.Default != "" && this.Default[0:1] == "{" {
				return "{}"
			} else if this.Default == "" {
				return "{}"
			}

			return this.Default
		}
		b, _ := json.Marshal(v)
		return string(b)
	}

	return strings.Trim(cast.ToString(v), " ")
}

func (this ResField) GetRawDefault(driver string) db.Expression {
	if this.DataType == "json" && this.Default == "" {
		return db.Raw("'{}'")
	}

	if len(this.Default) > 4 && strings.ToLower(this.Default[0:4]) == "sql:" {
		sql := strings.ToLower(this.Default[4:])
		if sql == "uuid()" {
			if driver == "sqlite" {
				return db.Raw("'" + uuid.NewString() + "'")
			}
			return db.Raw("uuid()")
		}
		return db.Raw(sql)
	}

	if this.Default == "" {
		if this.GetDataType() == contracts.ResDataType_Date || this.GetDataType() == contracts.ResDataType_Datetime {
			return db.Raw("NULL")
		}
		return db.Raw("''")
	}

	if strings.ToUpper(this.Default) == "CURRENT_TIMESTAMP" {
		return db.Raw(this.Default)
	}

	return db.Raw("'" + this.Default + "'")
}

func (this ResField) ToBlueprint(table *schema.Blueprint) (temp *schema.ColumnDefinition) {
	isNull := false
	comment := this.Name
	def := any(this.Default)

	if this.Comment != "" {
		comment += " [ " + strings.Trim(this.Comment, `' "`) + " ]"
	}

	switch this.DataType {
	case "string":
		len := 255
		if v, err := strconv.Atoi(strings.Trim(this.Length, `' "`)); err == nil {
			len = v
		}
		temp = table.String(this.Code, len)
	case "smallInteger":
		// integer 默认长度 4
		temp = table.SmallInteger(this.Code)
	case "boolean":
		// integer 默认长度 1
		temp = table.Boolean(this.Code)
	case "integer":
		// integer 默认长度 11
		temp = table.Integer(this.Code)
	case "date", "dateTime":
		if this.DataType == "date" {
			temp = table.Date(this.Code)
		} else {
			temp = table.DateTime(this.Code)
		}

		if strings.ToUpper(this.Default) == "CURRENT_TIMESTAMP" {
			def = db.Raw(this.Default)
		} else if def == "" {
			isNull = true
		}
	case "decimal":
		allowed := strings.SplitN(this.Length, ",", 2)
		total := 8
		places := 2
		if v, err := strconv.Atoi(strings.Trim(allowed[0], `' "`)); err == nil {
			total = v
		}
		if v, err := strconv.Atoi(strings.Trim(allowed[1], `' "`)); err == nil {
			places = v
		}
		temp = table.Decimal(this.Code, total, places)
	case "enum":
		allowed := []string{}
		for _, v := range strings.Split(this.Length, ",") {
			allowed = append(allowed, strings.Trim(v, `' "`))
		}
		temp = table.Enum(this.Code, allowed)
	case "json":
		temp = table.Json(this.Code)
		isNull = true
	case "text":
		temp = table.Text(this.Code)
		isNull = true
	}

	if isNull {
		temp.Nullable()
	} else {
		temp.Default(def)
	}

	temp.Comment(comment)

	return temp
}

func (this ResField) ToQueryField(t contracts.QueryDataType, alias string, options int) contracts.QueryField {
	o := &QueryField{
		ResField: this,
		typ:      t,
		alias:    alias,
	}

	return o.SetOptions(options)
}