package table

import (
	"fmt"

	"github.com/yaoapp/gou"
	"github.com/yaoapp/kun/any"
	"github.com/yaoapp/kun/log"
	"github.com/yaoapp/kun/maps"
	"github.com/yaoapp/yao/widgets/action"
)

var processActionDefaults = map[string]*action.Process{

	"Setting": {
		Name:    "yao.table.Setting",
		Process: "yao.table.Xgen",
		Default: []interface{}{nil},
	},
	"Component": {
		Name:    "yao.table.Component",
		Default: []interface{}{nil, nil, nil},
	},
	"Search": {
		Name:    "yao.table.Search",
		Default: []interface{}{nil, 1, 20},
	},
	"Get": {
		Name:    "yao.table.Get",
		Default: []interface{}{nil},
	},
	"Find": {
		Name:    "yao.table.Find",
		Default: []interface{}{nil, nil},
	},
	"Save": {
		Name:    "yao.table.Save",
		Default: []interface{}{nil},
	},
	"Create": {
		Name:    "yao.table.Create",
		Default: []interface{}{nil},
	},
	"Insert": {
		Name:    "yao.table.Insert",
		Default: []interface{}{nil, nil},
	},
	"Update": {
		Name:    "yao.table.Update",
		Default: []interface{}{nil, nil},
	},
	"UpdateWhere": {
		Name:    "yao.table.UpdateWhere",
		Default: []interface{}{nil, nil},
	},
	"UpdateIn": {
		Name:    "yao.table.UpdateIn",
		Default: []interface{}{nil, nil},
	},
	"Delete": {
		Name:    "yao.table.Delete",
		Default: []interface{}{nil},
	},
	"DeleteWhere": {
		Name:    "yao.table.DeleteWhere",
		Default: []interface{}{nil},
	},
	"DeleteIn": {
		Name:    "yao.table.DeleteIn",
		Default: []interface{}{nil},
	},
}

// SetDefaultProcess set the default value of action
func (act *ActionDSL) SetDefaultProcess() {

	act.Setting = action.NewProcess("Setting", act.Setting).
		SetDefault(processActionDefaults).
		SetHandler(processHandler)

	act.Component = action.NewProcess("Component", act.Component).
		SetDefault(processActionDefaults).
		SetHandler(processHandler)

	act.Search = action.NewProcess("Search", act.Search).
		WithBefore(act.BeforeSearch).WithAfter(act.AfterSearch).
		SetDefault(processActionDefaults).
		SetHandler(processHandler)

	act.Get = action.NewProcess("Get", act.Get).
		WithBefore(act.BeforeGet).WithAfter(act.AfterGet).
		SetDefault(processActionDefaults).
		SetHandler(processHandler)

	act.Find = action.NewProcess("Find", act.Find).
		WithBefore(act.BeforeFind).
		WithAfter(act.AfterFind).
		SetDefault(processActionDefaults).
		SetHandler(processHandler)

	act.Save = action.NewProcess("Save", act.Save).
		WithBefore(act.BeforeSave).WithAfter(act.AfterSave).
		SetDefault(processActionDefaults).
		SetHandler(processHandler)

	act.Create = action.NewProcess("Create", act.Create).
		WithBefore(act.BeforeCreate).WithAfter(act.AfterCreate).
		SetDefault(processActionDefaults).
		SetHandler(processHandler)

	act.Insert = action.NewProcess("Insert", act.Insert).
		WithBefore(act.BeforeInsert).WithAfter(act.AfterInsert).
		SetDefault(processActionDefaults).
		SetHandler(processHandler)

	act.Update = action.NewProcess("Update", act.Update).
		WithBefore(act.BeforeUpdate).WithAfter(act.AfterUpdate).
		SetDefault(processActionDefaults).
		SetHandler(processHandler)

	act.UpdateWhere = action.NewProcess("UpdateWhere", act.UpdateWhere).
		WithBefore(act.BeforeUpdateWhere).WithAfter(act.AfterUpdateWhere).
		SetDefault(processActionDefaults).
		SetHandler(processHandler)

	act.UpdateIn = action.NewProcess("UpdateIn", act.UpdateIn).
		WithBefore(act.BeforeUpdateIn).WithAfter(act.AfterUpdateIn).
		SetDefault(processActionDefaults).
		SetHandler(processHandler)

	act.Delete = action.NewProcess("Delete", act.Delete).
		WithBefore(act.BeforeDelete).WithAfter(act.AfterDelete).
		SetDefault(processActionDefaults).
		SetHandler(processHandler)

	act.DeleteWhere = action.NewProcess("DeleteWhere", act.DeleteWhere).
		WithBefore(act.BeforeDeleteWhere).WithAfter(act.AfterDeleteWhere).
		SetDefault(processActionDefaults).
		SetHandler(processHandler)

	act.DeleteIn = action.NewProcess("DeleteIn", act.DeleteIn).
		WithBefore(act.BeforeDeleteIn).WithAfter(act.AfterDeleteIn).
		SetDefault(processActionDefaults).
		SetHandler(processHandler)
}

// BindModel bind model
func (act *ActionDSL) BindModel(m *gou.Model) {

	name := m.ID
	act.Search.Bind(fmt.Sprintf("models.%s.Paginate", name))
	act.Get.Bind(fmt.Sprintf("models.%s.Get", name))
	act.Find.Bind(fmt.Sprintf("models.%s.Find", name))
	act.Save.Bind(fmt.Sprintf("models.%s.Save", name))
	act.Create.Bind(fmt.Sprintf("models.%s.Create", name))
	act.Insert.Bind(fmt.Sprintf("models.%s.Insert", name))
	act.Update.Bind(fmt.Sprintf("models.%s.Update", name))
	act.UpdateWhere.Bind(fmt.Sprintf("models.%s.UpdateWhere", name))
	act.UpdateIn.Bind(fmt.Sprintf("models.%s.UpdateWhere", name))
	act.Delete.Bind(fmt.Sprintf("models.%s.Delete", name))
	act.DeleteWhere.Bind(fmt.Sprintf("models.%s.DeleteWhere", name))
	act.DeleteIn.Bind(fmt.Sprintf("models.%s.DeleteWhere", name))

	// bind options
	if act.Bind.Option != nil {
		act.Search.Default[0] = act.Bind.Option
		act.Get.Default[0] = act.Bind.Option
		act.Find.Default[1] = act.Bind.Option
	}
}

func processHandler(p *action.Process, process *gou.Process) (interface{}, error) {

	tab, err := Get(process)
	if err != nil {
		return nil, err
	}
	args := p.Args(process)

	// Process
	name := p.Process
	if name == "" {
		name = p.ProcessBind
	}

	if name == "" {
		log.Error("[table] %s %s process is required", tab.ID, p.Name)
		return nil, fmt.Errorf("[table] %s %s process is required", tab.ID, p.Name)
	}

	// Before Hook
	if p.Before != nil {
		log.Trace("[table] %s %s before: exec(%v)", tab.ID, p.Name, args)
		newArgs, err := p.Before.Exec(args, process.Sid, process.Global)
		if err != nil {
			log.Error("[table] %s %s before: %s", tab.ID, p.Name, err.Error())
		} else {
			log.Trace("[table] %s %s before: args:%v", tab.ID, p.Name, args)
			args = newArgs
		}
	}

	// Compute In
	switch p.Name {
	case "yao.table.Save", "yao.table.Create":
		switch args[0].(type) {
		case map[string]interface{}:
			data := args[0].(map[string]interface{})
			err := tab.computeSave(process, data)
			if err != nil {
				log.Error("[table] %s %s -> %s %s", tab.ID, p.Name, name, err.Error())
			}
			args[0] = data
		}
		break

	case "yao.table.Update", "yao.table.UpdateWhere", "yao.table.UpdateIn":
		switch args[1].(type) {
		case map[string]interface{}:
			data := args[1].(map[string]interface{})
			err := tab.computeSave(process, data)
			if err != nil {
				log.Error("[table] %s %s -> %s %s", tab.ID, p.Name, name, err.Error())
			}
			args[1] = data
		}
		break

	case "yao.table.Insert":
		break
	}

	// Execute Process
	act, err := gou.ProcessOf(name, args...)
	if err != nil {
		log.Error("[table] %s %s -> %s %s", tab.ID, p.Name, name, err.Error())
		return nil, fmt.Errorf("[table] %s %s -> %s %s", tab.ID, p.Name, name, err.Error())
	}

	res, err := act.WithGlobal(process.Global).WithSID(process.Sid).Exec()
	if err != nil {
		log.Error("[table] %s %s -> %s %s", tab.ID, p.Name, name, err.Error())
		return nil, fmt.Errorf("[table] %s %s -> %s %s", tab.ID, p.Name, name, err.Error())
	}

	// Compute Out
	switch p.Name {

	case "yao.table.Search":
		if newMap, ok := res.(map[string]interface{}); ok {
			err := tab.computeSearch(process, newMap, "data")
			if err != nil {
				log.Error("[table] %s %s -> %s %s", tab.ID, p.Name, name, err.Error())
			}
			res = newMap
		}
		break

	case "yao.table.Get":
		if _, ok := res.([]maps.MapStr); ok {
			data := []interface{}{}
			for _, v := range res.([]maps.MapStr) {
				data = append(data, map[string]interface{}(v))
			}
			res = data
		}

		if data, ok := res.([]interface{}); ok {

			err := tab.computeGet(process, data)
			if err != nil {
				log.Error("[table] %s %s -> %s %s", tab.ID, p.Name, name, err.Error())
			}
			res = data
		}
		break

	case "yao.table.Find":
		switch res.(type) {
		case map[string]interface{}, maps.MapStr:
			data := any.MapOf(res).MapStrAny
			err := tab.computeFind(process, data)
			if err != nil {
				log.Error("[table] %s %s -> %s %s", tab.ID, p.Name, name, err.Error())
			}
			res = data
		}
		break
	}

	// After hook
	if p.After != nil {
		log.Trace("[table] %s %s after: exec(%v)", tab.ID, p.Name, res)
		newRes, err := p.After.Exec(res, process.Sid, process.Global)
		if err != nil {
			log.Error("[table] %s %s after: %s", tab.ID, p.Name, err.Error())
		} else {
			log.Trace("[table] %s %s after: %v", tab.ID, p.Name, newRes)
			res = newRes
		}
	}

	return res, nil
}
