package common

import (
	"dbweb/core"
	"dbweb/lib/safe"
	"dbweb/modules/common/recordview"
	"fmt"
	"strings"

	"encoding/gob"
	"encoding/json"
	"net/http"

	"github.com/linlexing/dbx/ddb"

	"github.com/linlexing/dbx/data"
	"github.com/linlexing/dbx/schema"
)

const (
	CtrlUpdateName = "update"
)

type updateTaskParam struct {
	DBName        string
	Table         string
	DataSQL       string
	Sets          []data.UpdateSet
	AdditionSet   string
	AdditionWhere string
	BeforeSQL     string
}
type Update struct{}
type UpdateFieldValueListItem struct {
	Value string
	Label string
}
type UpdateFieldSetting struct {
	Name      string
	Label     string
	Tooltip   string
	Freeentry bool                        //是否允许自由录入
	Must      bool                        //必填
	List      []*UpdateFieldValueListItem //nil-自由录入；其他是限定值
}
type UpdateParam struct {
	DB            string
	TableName     string
	Fields        []*UpdateFieldSetting
	AdditionSet   string //附加的set子句
	AdditionWhere string //附加的Where条件，只有满足这些条件的记录才会被更新
	BeforeSQL     string //附加的update之前运行的SQL语句
}

//ModelUpdateElement 一个更新的工作元素定义
type ModelUpdateElement struct {
	core.ModelElementNoParam `yaml:",inline"`
	Params                   UpdateParam `dbx:"STR"`
}

func init() {
	gob.Register(new(UpdateParam))
	core.RegisterFun(CtrlUpdateName, new(Update), "_layout/blank")
	core.RegisterElementType(CtrlUpdateName, ModelUpdateElement{})
}
func (m *Update) ElementAjaxProcess(p *core.BillGetHandleArgs) {
	query := p.Req.URL.Query()
	switch query.Get("action") {
	case "getTableList":
		db := core.LoadOuterDB(p.DB, query.Get("db"))
		names, err := schema.Find(db.DriverName()).TableNames(db)
		if err != nil {
			core.LOG.Panic(err)
		}
		list := append([]string{"*"}, names...)

		p.Render.JSON(http.StatusOK, list)
	case "getFields":
		db := core.LoadOuterDB(p.DB, query.Get("db"))
		table, err := data.OpenTable(db.DriverName(), db, query.Get("table"))
		if err != nil {
			core.LOG.Panic(err)
		}
		p.Render.JSON(http.StatusOK, table.ColumnNames)
	default:

		core.LOG.Panic("not impl" + query.Get("action"))
	}

}
func (r *Update) ElementParamRender(p *core.BillGetHandleArgs) {
	jsonParams := UpdateParam{}
	if bys := safe.Bytea(p.Record.Main["PARAMS"]); bys != nil {
		if err := json.Unmarshal(bys, &jsonParams); err != nil {
			core.LOG.Println(string(bys))
			core.LOG.Panic(err)
		}
	}
	dbNames, err := ddb.ScanStrings(p.DB, "select NAME from outerdb order by name")
	if err != nil {
		core.LOG.Panic(err)
	}
	newList := []string{""}
	dbNames = append(newList, dbNames...)
	p.More["OuterdbNames"] = dbNames
	tabledb := core.LoadOuterDB(p.DB, safe.String(jsonParams.DB))

	names, err := schema.Find(tabledb.DriverName()).TableNames(tabledb)
	if err != nil {
		core.LOG.Panic(err)
	}
	list := append([]string{"*"}, names...)

	p.More["TableNames"] = list
	p.More["JSONParams"] = jsonParams
}
func (u *Update) Get(p *core.ElementHandleArgs) {
	callParams := recordview.GetCallParam(p)
	if callParams == nil {
		p.RenderTimeout()
		return
	}
	fromEle := callParams.FromRecordView(p.DB)
	fromRecordViewParam := fromEle.Params
	p.More["FromElement"] = fromEle
	db := core.LoadOuterDB(p.DB, callParams.Select.DBName)

	//取出定义中的字段
	var upParam UpdateParam
	if err := json.Unmarshal(p.Element.Params, &upParam); err != nil {
		core.LOG.Panic(err)
	}
	if upParam.TableName != "*" && upParam.TableName != fromRecordViewParam.TableName {
		core.LOG.Panic(fmt.Errorf("table:%s <> %s", fromRecordViewParam.TableName, upParam.TableName))
	}
	var num int64
	strSql := fmt.Sprintf("select count(*) from (%s) aa", callParams.Select.DataSQL())
	if len(upParam.AdditionWhere) > 0 {
		strSql += " where " + upParam.AdditionWhere
	}
	if err := db.QueryRow(strSql).Scan(&num); err != nil {
		core.LOG.Panic(err)
	}
	p.More["SrcRowCount"] = num

	fields := upParam.Fields
	//*号的需要取出所有字段
	if upParam.TableName == "*" {
		fields = []*UpdateFieldSetting{}
		tab, err := schema.Find(db.DriverName()).OpenTable(db, fromRecordViewParam.TableName)
		if err != nil {
			core.LOG.Panic(err)
		}
		for _, v := range tab.Columns {
			fields = append(fields, &UpdateFieldSetting{
				Name:      v.Name,
				Freeentry: true,
			})
		}
	}
	//允许自由录入的，需要加上各类型字段的常用选择项
	table, err := schema.Find(db.DriverName()).OpenTable(db, fromRecordViewParam.TableName)
	if err != nil {
		core.LOG.Panic(err)
	}
	trimFun := ""
	nowFun := ""
	dateFun := ""
	switch db.DriverName() {
	case "oci8":
		trimFun = "TRIM"
		nowFun = "SYSDATE"
		dateFun = "TRUNC(SYSDATE)"
	case "mysql":
		trimFun = "TRIM"
		nowFun = "NOW()"
		dateFun = "CURDATE()"
	case "postgres":
		trimFun = "TRIM"
		nowFun = "NOW()"
		dateFun = "CURRENT_DATE"
	default:
		core.LOG.Panic("not impl")
	}
	for _, field := range fields {
		if field.Freeentry {
			if field.List == nil {
				field.List = []*UpdateFieldValueListItem{}
			}
			switch table.ColumnByName(field.Name).Type {
			case schema.TypeString:
				field.List = append(field.List, &UpdateFieldValueListItem{
					Label: "空值",
					Value: "``",
				}, &UpdateFieldValueListItem{
					Label: "清除前后空格",
					Value: fmt.Sprintf("`%s(%s)`", trimFun, field.Name),
				})
			case schema.TypeBytea:
				field.List = append(field.List, &UpdateFieldValueListItem{
					Label: "空值",
					Value: "``",
				})
			case schema.TypeDatetime:
				field.List = append(field.List, &UpdateFieldValueListItem{
					Label: "空值",
					Value: "``",
				}, &UpdateFieldValueListItem{
					Label: "当前时间",
					Value: fmt.Sprintf("`%s`", nowFun),
				}, &UpdateFieldValueListItem{
					Label: "当前日期",
					Value: fmt.Sprintf("`%s`", dateFun),
				})
			case schema.TypeFloat:
				field.List = append(field.List, &UpdateFieldValueListItem{
					Label: "空值",
					Value: "``",
				})
			case schema.TypeInt:
				field.List = append(field.List, &UpdateFieldValueListItem{
					Label: "空值",
					Value: "``",
				})

			}
		}
	}
	p.More["Fields"] = fields
	p.HTML()
}

//Post 业务
func (u *Update) Post(p *core.ElementHandleArgs) {
	callParams := recordview.GetCallParam(p)
	if callParams == nil {
		p.RenderTimeout()
		return
	}
	db := core.LoadOuterDB(p.DB, callParams.Select.DBName)
	fromEle := callParams.FromRecordView(p.DB)
	fromRecordViewParam := fromEle.Params
	var upParam UpdateParam
	if err := json.Unmarshal(p.Element.Params, &upParam); err != nil {
		core.LOG.Panic(err)
	}

	p.Req.ParseForm()
	sets := []data.UpdateSet{}
	for k, v := range p.Req.PostForm {
		names := strings.Split(k, "_")
		if names[0] == "sel" && v[0] == "on" {
			sets = append(sets, data.UpdateSet{Column: names[1], Value: p.Req.PostForm.Get("val_" + names[1])})
		}
	}
	table, err := schema.Find(db.DriverName()).OpenTable(db, fromRecordViewParam.TableName)
	if err != nil {
		core.LOG.Panic(err)
	}

	up := data.Update{
		Table:            table,
		DataSQL:          callParams.Select.DataSQL(),
		DataUniqueFields: fromRecordViewParam.UniqueField,
		Sets:             sets,
		AdditionSet:      upParam.AdditionSet,
		AdditionWhere:    upParam.AdditionWhere,
		BeforeSQL:        upParam.BeforeSQL,
		SQLRenderArgs: map[string]interface{}{
			"Driver": db.DriverName(),
			"User":   p.User,
			"Req":    p.Req,
		},
	}
	pam := updateTaskParam{
		DBName:        callParams.Select.DBName,
		Table:         fromRecordViewParam.TableName,
		DataSQL:       callParams.Select.DataSQL(),
		Sets:          sets,
		AdditionSet:   upParam.AdditionSet,
		AdditionWhere: upParam.AdditionWhere,
		BeforeSQL:     upParam.BeforeSQL,
	}
	bkTask := &core.TaskRun{
		Db:       db,
		Name:     fmt.Sprintf("%s-%s", p.Element.DisplayLabel(), fromEle.DisplayLabel()),
		User:     p.User,
		ClientIP: p.Req.RemoteAddr,
		Param:    pam,
		Func: func(t *core.TaskRun) (result string) {
			if i, err := up.Exec(db); err != nil {
				core.LOG.Panic(err)

			} else {
				result = fmt.Sprintf("%d record updated.", i)
			}
			return
		},
	}
	if err := bkTask.GoRun(); err != nil {
		p.RenderError(err.Error())

	} else {
		p.RenderMessage(fmt.Sprintf("更新操作将在后台运行，可以<a href='%s' target='_blank'>点击查看进度</a>", p.User.Sign("/browsetask/"+bkTask.ID())))
	}
}
