package crud

import (
	"context"
	"encoding/csv"
	"encoding/json"
	"fmt"
	"git.nspix.com/golang/micro/gateway/http"
	"git.nspix.com/golang/rest/orm/query"
	"git.nspix.com/golang/rest/orm/schema"
	"git.nspix.com/golang/rest/orm/validator"
	"git.nspix.com/golang/rest/scenario"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"reflect"
	"strconv"
	"strings"
	"sync/atomic"
	"time"
)

var seq int64

const (
	ScenarioCreate string = "create"
	ScenarioUpdate        = "update"
	ScenarioDelete        = "delete"
	ScenarioExport        = "export"
	ScenarioList          = "list"
	ScenarioView          = "view"
)

type Entity struct {
	Module    string
	db        *gorm.DB
	enable    *int32
	formatter *Formatter
	refType   reflect.Type
	refValue  reflect.Value
	stmt      *gorm.Statement
	ctx       context.Context
	prefix    string
	naming    struct {
		label    string
		plural   string
		singular string
	}
	seq       int64
	Value     interface{}
	Scenarios []string
	Urls      map[string]string
	Callbacks Callbacks
	instance  *CRUD
}

type (
	Entities []*Entity
)

func (e Entities) Len() int {
	return len(e)
}

func (e Entities) Less(i, j int) bool {
	return e[i].seq < e[j].seq
}

func (e Entities) Swap(i, j int) {
	e[i], e[j] = e[j], e[i]
}

func (e *Entity) primaryKey() string {
	if e.stmt.Schema != nil {
		if len(e.stmt.Schema.PrimaryFieldDBNames) > 0 {
			return e.stmt.Schema.PrimaryFieldDBNames[0]
		}
	}
	return "id"
}

func (e *Entity) hasScenarios(name string) bool {
	for _, s := range e.Scenarios {
		if name == s {
			return true
		}
	}
	return false
}

func (e *Entity) buildConditions(ctx *http.Context, search *query.Query, fields []*schema.Schema, val interface{}, cb EachQueryCallback) {
	var (
		err error
	)
	for _, field := range fields {
		if cb != nil {
			if err = cb(val, field, search, ctx); err != nil {
				continue
			}
		}
		if fcb := e.instance.getCallbackByFormat(field.Format); fcb != nil {
			if err = fcb(val, field, search, ctx); err != nil {
				continue
			}
		}
		switch field.Format {
		case "text", "textarea", "string":
			search.AndFilterWhere(query.NewConditionWithOperator("like", field.Column, ctx.FormValue(field.Column)))
		case "datetime", "date", "time":
			ss := make([]string, 0)
			formValue := ctx.FormValue(field.Column)
			if formValue != "" {
				if err := json.Unmarshal([]byte(ctx.FormValue(field.Column)), &ss); err == nil {
					if len(ss) == 2 {
						search.AndFilterWhere(
							query.NewConditionWithOperator(">=", field.Column, ss[0]),
							query.NewConditionWithOperator("<=", field.Column, ss[1]),
						)
					} else if len(ss) == 1 {
						search.AndFilterWhere(
							query.NewConditionWithOperator("=", field.Column, ss[0]),
						)
					}
				} else {
					var sep string
					seps := []byte{',', '/'}
					for _, s := range seps {
						if strings.IndexByte(formValue, s) > -1 {
							sep = string(s)
						}
					}
					if ss := strings.Split(formValue, sep); len(ss) == 2 {
						search.AndFilterWhere(
							query.NewConditionWithOperator(">=", field.Column, strings.TrimSpace(ss[0])),
							query.NewConditionWithOperator("<=", field.Column, strings.TrimSpace(ss[1])),
						)
					}
				}
			}
		case "duration", "number", "integer", "decimal":
			ss := make([]string, 0)
			formValue := ctx.FormValue(field.Column)
			if formValue != "" {
				if json.Valid([]byte(field.Column)) {
					if err := json.Unmarshal([]byte(ctx.FormValue(field.Column)), &ss); err == nil {
						if len(ss) == 2 {
							switch ss[0] {
							case ">", ">=", "<", "<=", "=":
								search.AndWhere(query.NewConditionWithOperator(ss[0], field.Column, ss[1]))
							default:
								search.AndWhere(query.NewConditionWithOperator("=", field.Column, ss[1]))
							}
						} else if len(ss) == 1 {
							search.AndWhere(
								query.NewConditionWithOperator("=", field.Column, ss[0]),
							)
						}
					}
				} else {
					search.AndWhere(query.NewConditionWithOperator("=", field.Column, formValue))
				}
			}
		default:
			if field.Type == "string" {
				search.AndFilterWhere(query.NewConditionWithOperator("like", field.Column, ctx.FormValue(field.Column)))
			} else {
				search.AndFilterWhere(query.NewConditionWithOperator("=", field.Column, ctx.FormValue(field.Column)))
			}
		}
	}
}

//构建排序规则
func (e *Entity) buildSortable(ctx *http.Context, search *query.Query) {
	sortPar := ctx.FormValue("sort")
	if sortPar != "" {
		sorts := strings.Split(sortPar, ",")
		for _, s := range sorts {
			if s[0] == '-' {
				search.OrderBy(s[1:], "DESC")
			} else {
				if s[0] == '+' {
					search.OrderBy(s[1:], "ASC")
				} else {
					search.OrderBy(s, "ASC")
				}
			}
		}
	}
}

func (e *Entity) actionIndex(c *http.Context) (err error) {
	page, _ := strconv.Atoi(c.FormValue("page"))
	pagesize, _ := strconv.Atoi(c.FormValue("pagesize"))
	if pagesize <= 0 {
		pagesize = 15
	}
	pv := page
	if pv > 0 {
		pv--
	}
	sliceValue := reflect.MakeSlice(reflect.SliceOf(e.refType), 0, 0)
	models := reflect.New(sliceValue.Type())
	models.Elem().Set(sliceValue)
	search := query.New(e.db)
	if e.Callbacks.BeforeQuery != nil {
		if err = e.Callbacks.BeforeQuery(e.Value, search, c); err != nil {
			return c.Error(8004, err.Error())
		}
	}
	searchSchemas := schema.VisibleField(e.Module, e.stmt.Table, scenario.Search)
	listSchemas := schema.VisibleField(e.Module, e.stmt.Table, scenario.List)
	e.buildConditions(c, search, searchSchemas, e.Value, e.Callbacks.EachQuery)
	e.buildSortable(c, search)
	search.Offset(pv * pagesize).Limit(pagesize)
	//添加排序支持
	if err = search.All(models.Interface()); err != nil {
		return c.Error(8004, err.Error())
	}
	return c.Success(map[string]interface{}{
		"page":       page,
		"pageSize":   pagesize,
		"totalCount": search.Limit(0).Offset(0).Count(e.Value),
		"data":       e.formatter.formatSchemas(models.Interface(), listSchemas, e.stmt),
	})
}

func (e *Entity) actionCreate(c *http.Context) (err error) {
	val := reflect.New(e.refType).Interface()
	if err = c.Bind(val); err != nil {
		return c.Error(8002, err.Error())
	}
	if e.Callbacks.BeforeInsert != nil {
		if err = e.Callbacks.BeforeInsert(val, c); err != nil {
			return c.Error(8004, err.Error())
		}
	}
	if e.Callbacks.BeforeSave != nil {
		if err = e.Callbacks.BeforeSave(val, c); err != nil {
			return c.Error(8004, err.Error())
		}
	}
	sess := e.db.Create(val)
	if err = sess.Error; err != nil {
		if err, ok := sess.Error.(*validator.StructError); ok {
			return c.Error(1001, err.Error())
		} else {
			return c.Error(8004, sess.Error.Error())
		}
	}
	if e.Callbacks.AfterInsert != nil {
		_ = e.Callbacks.AfterInsert(val, c)
	}
	if e.Callbacks.AfterSave != nil {
		_ = e.Callbacks.AfterSave(val, c)
	}
	return c.Success(map[string]interface{}{
		"id":    schema.PrimaryKeyValue(sess.Statement),
		"state": "success",
	})
}

func (e *Entity) actionUpdate(c *http.Context) (err error) {
	idStr := c.ParamValue("id")
	val := reflect.New(e.refType).Interface()
	if err = e.db.Where(e.primaryKey()+"=?", idStr).First(val).Error; err != nil {
		return c.Error(8004, err.Error())
	}
	if err = c.Bind(val); err != nil {
		return c.Error(8002, err.Error())
	}
	if e.Callbacks.BeforeUpdate != nil {
		if err = e.Callbacks.BeforeUpdate(val, c); err != nil {
			return c.Error(8004, err.Error())
		}
	}
	if e.Callbacks.BeforeSave != nil {
		if err = e.Callbacks.BeforeSave(val, c); err != nil {
			return c.Error(8004, err.Error())
		}
	}
	sess := e.db.Model(val).Updates(val)
	if sess.Error != nil {
		if err, ok := sess.Error.(*validator.StructError); ok {
			return c.Error(1001, err.Error())
		} else {
			return c.Error(8004, sess.Error.Error())
		}
	}
	if e.Callbacks.AfterUpdate != nil {
		_ = e.Callbacks.AfterUpdate(val, c)
	}
	if e.Callbacks.AfterSave != nil {
		_ = e.Callbacks.AfterSave(val, c)
	}
	return c.Success(map[string]interface{}{
		"id":    idStr,
		"state": "success",
	})
}

func (e *Entity) actionDelete(c *http.Context) (err error) {
	idStr := c.ParamValue("id")
	val := reflect.New(e.refType).Interface()
	if err = e.db.Where(e.primaryKey()+"=?", idStr).First(val).Error; err != nil {
		return c.Error(8004, err.Error())
	}
	if e.Callbacks.BeforeDelete != nil {
		if err = e.Callbacks.BeforeDelete(val, c); err != nil {
			return c.Error(8004, err.Error())
		}
	}
	if err = e.db.Where(e.primaryKey()+"=?", c.ParamValue("id")).Model(val).Delete(val).Error; err == nil {
		if e.Callbacks.AfterDelete != nil {
			_ = e.Callbacks.AfterDelete(val, c)
		}
		return c.Success(map[string]interface{}{
			"state": "success",
			"id":    idStr,
		})
	} else {
		return c.Error(8009, err.Error())
	}
}

func (e *Entity) actionView(c *http.Context) (err error) {
	val := reflect.New(e.refType).Interface()
	if err = e.db.Where(e.primaryKey()+"=?", c.ParamValue("id")).First(val).Error; err != nil {
		return c.Error(8004, err.Error())
	}
	if c.FormValue("format") == "true" {
		listSchemas := schema.VisibleField(e.Module, e.stmt.Table, scenario.View)
		return c.Success(e.formatter.formatSchema(val, listSchemas, e.stmt))
	} else {
		return c.Success(val)
	}
}

func (e *Entity) actionExport(c *http.Context) (err error) {
	sliceValue := reflect.MakeSlice(reflect.SliceOf(e.refType), 0, 0)
	models := reflect.New(sliceValue.Type())
	models.Elem().Set(sliceValue)
	search := query.New(e.db)
	if e.Callbacks.BeforeQuery != nil {
		if err = e.Callbacks.BeforeQuery(e.Value, search, c); err != nil {
			return c.Error(8004, err.Error())
		}
	}
	searchSchemas := schema.VisibleField(e.Module, e.stmt.Table, scenario.Search)
	e.buildConditions(c, search, searchSchemas, e.Value, e.Callbacks.EachQuery)
	//添加排序支持
	e.buildSortable(c, search)
	if err = search.All(models.Interface()); err != nil {
		return c.Error(8004, err.Error())
	}
	c.Response().Header().Set("Content-Type", "text/csv")
	c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment;filename=%s.csv", e.naming.plural+"-"+time.Now().Format("20060102")))
	exportSchemas := schema.VisibleField(e.Module, e.stmt.Table, scenario.List)
	value := e.formatter.formatSchemas(models.Interface(), exportSchemas, e.stmt)
	writer := csv.NewWriter(c.Response())
	ss := make([]string, len(exportSchemas))
	for i, field := range exportSchemas {
		ss[i] = field.Label
	}
	_ = writer.Write(ss)
	if values, ok := value.([]interface{}); ok {
		for _, val := range values {
			row, ok2 := val.(map[string]interface{})
			if !ok2 {
				continue
			}
			for i, field := range exportSchemas {
				if v, ok := row[field.Column]; ok {
					ss[i] = fmt.Sprint(v)
				} else {
					ss[i] = ""
				}
			}
			_ = writer.Write(ss)
		}
	}
	writer.Flush()
	return
}

func (e *Entity) SetModule(module string) {
	e.Module = module
}

func newEntity(ctx context.Context, value interface{}, db *gorm.DB) *Entity {
	if ctx == nil {
		ctx = context.Background()
	}
	e := &Entity{
		db:    db,
		ctx:   ctx,
		Value: value,
		Urls:  make(map[string]string),
		seq:   atomic.AddInt64(&seq, 1),
	}
	e.stmt = &gorm.Statement{
		DB:       db,
		ConnPool: db.ConnPool,
		Context:  ctx,
		Clauses:  map[string]clause.Clause{},
	}
	if err := e.stmt.Parse(value); err != nil {
		panic(err.Error())
	}
	e.refValue = reflect.Indirect(reflect.ValueOf(value))
	e.refType = e.refValue.Type()
	return e
}
