package core

import (
	"errors"
	"fmt"
	"os"
	"reflect"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/cdle/sillyplus/core/common"
	"github.com/cdle/sillyplus/core/storage"
	"github.com/cdle/sillyplus/utils"
	"github.com/dop251/goja"
	"github.com/dop251/goja/parser"
	"github.com/dop251/goja_nodejs/eventloop"
	"github.com/robfig/cron/v3"
)

type myFieldNameMapper struct{}

var mutexMap = make(map[string]*sync.Mutex)
var mutexMapMutex sync.Mutex

func GetMutex(uuid string) *sync.Mutex {
	mutexMapMutex.Lock()
	defer mutexMapMutex.Unlock()

	if mutex, ok := mutexMap[uuid]; ok {
		return mutex
	}

	mutex := &sync.Mutex{}
	mutexMap[uuid] = mutex
	return mutex
}

func (tfm myFieldNameMapper) FieldName(_ reflect.Type, f reflect.StructField) string {
	tag := f.Tag.Get(`json`)
	if idx := strings.IndexByte(tag, ','); idx != -1 {
		tag = tag[:idx]
	}
	if parser.IsIdentifier(tag) {
		return tag
	}
	return uncapitalize(f.Name)
}

func uncapitalize(s string) string {
	return strings.ToLower(s[0:1]) + s[1:]
}

func (tfm myFieldNameMapper) MethodName(_ reflect.Type, m reflect.Method) string {
	return uncapitalize(m.Name)
}

var RegistFuncs = map[string]interface{}{}

var plugins = MakeBucket("plugins")

type Route struct {
	Path      string  `json:"path"`
	Name      string  `json:"name"`
	Component string  `json:"component,omitempty"`
	Routes    []Route `json:"routes,omitempty"`
	// Key       string  `json:"key,omitempty"`
	CreateAt string `json:"create_at"`
}

func CancelPluginlistening(uuid string) {
	// logs.Info(`k, c.Function, c.Function.Rules`)
	for _, wait := range waits {
		wait.Foreach(func(k int64, c *Carry) bool {
			if uuid == c.UUID {
				c.Chan <- errors.New("uinstall")
			}
			return true
		})
	}
}

func initPlugins() {
	plugins.Foreach(func(key, data []byte) error {
		f, err := initPlugin(string(data), string(key))
		if err != nil {
			console.Error("初始化插件%s错误: %v", key, err)
		}
		AddCommand([]*common.Function{f})
		// os.WriteFile(fmt.Sprintf("%s/%s.js", pluginPath, f.Title), data, 0600)
		return nil
	})
	var pluginLock = new(sync.Mutex)
	storage.Watch(plugins, nil, func(old, new, key string) (fin *storage.Final) {
		pluginLock.Lock()
		defer pluginLock.Unlock()
		if new == "install" {
			for _, p := range plugin_list {
				if p.UUID != key {
					continue
				}
				script := string(fetchScript(p.Address, key))
				if f, _ := initPlugin(script, p.UUID); len(f.Rules) != 0 || f.Cron != "" || f.Module || f.OnStart {
					fin = &storage.Final{
						Now: script,
					}
					fin = &storage.Final{}
					su := &ScriptUtils{script: script}
					su.SetValue("origin", p.Organization)
					new = su.script
					break
				} else {
					return &storage.Final{
						Error: errors.New("订阅源异常。"),
					}
				}
			}
		}
		if new != "" {
			fin = &storage.Final{}
			su := &ScriptUtils{script: new}
			if version := su.GetValue("version"); version == "" || regexp.MustCompile(`v\d+\.\d+\.\d`).FindString(version) != version {
				su.SetValue("version", "v1.0.0")
			}
			if auhtor := su.GetValue("author"); auhtor == "" {
				su.SetValue("author", "佚名")
			}
			if su.GetValue("description") == "" {
				su.SetValue("description", "🐒这个人很懒什么都没有留下。")
			} //module
			title := su.GetValue("title")
			if title == "" {
				su.SetValue("title", "无名脚本")
			}
			if title != "无名脚本" && title != "" {
				onStart := su.GetValue("on_start")
				if onStart != "true" {
					module := su.GetValue("module")
					if module != "true" {
						if module != "" {
							su.DeleteValue("module")
						}
						web := su.GetValue("web")
						if web != "true" {
							if web != "" {
								su.DeleteValue("web")
							}
							// rule := su.GetValue("rule")
							// if rule == "" {
							// su.SetValue("rule", title)
							// }
						} else {
							su.DeleteValue("rule")
							su.DeleteValue("cron")
							su.DeleteValue("admin")
							su.DeleteValue("priority")
							su.DeleteValue("platform")
						}
					} else {
						su.DeleteValue("rule")
						su.DeleteValue("cron")
						su.DeleteValue("web")
						su.DeleteValue("admin")
						su.DeleteValue("priority")
						su.DeleteValue("platform")
					}
				} else {
					su.DeleteValue("rule")
					su.DeleteValue("cron")
					su.DeleteValue("web")
					su.DeleteValue("admin")
					su.DeleteValue("priority")
					su.DeleteValue("platform")
					su.DeleteValue("module")
				}
			}
			create_at := su.GetValue("create_at")
			if _, err := time.Parse("2006-01-02 15:04:05", create_at); err != nil {
				su.SetValue("create_at", time.Now().Format("2006-01-02 15:04:05"))
			}
			fin.Now = su.script
			if su.script != new {
				fin.Message = su.script
			} else if title != (&ScriptUtils{script: old}).GetValue("title") {
				fin.Message = "标题变更。"
			}
			new = su.script
		}
		f, err := initPlugin(new, key)
		if err != nil && new != "" {
			console.Error(err)
		}
		apd := false
		for i := range Functions {
			if Functions[i].UUID == key {
				DestroyAdapterByUUID(key)
				Functions[i].Running = false
				if Functions[i].CronId != 0 {
					C.Remove(cron.EntryID(Functions[i].CronId))

				}
				Functions = append(Functions[:i], Functions[i+1:]...)
				CancelPluginCrons(key)
				CancelPluginWebs(key)
				CancelPluginlistening(key)
				CancelHttpListen(key)
				storage.DisableHandle(key)
				if new != "" {
					AddCommand([]*common.Function{f})
					if old == "" {
						console.Log("已加载 %s.js", f.Title)
					} else if !f.OnStart {
						console.Log("已重载 %s.js", f.Title)
					}
				} else {
					of, _ := initPlugin(old, key)
					console.Log("已卸载 %s.js", of.Title)
				}
				apd = true
				break
			}
		}
		if !apd {
			AddCommand([]*common.Function{f})
		}
		if f.UUID != "" && f.Public {
			go func() {
				os.WriteFile(fmt.Sprintf("%s/%s.js", plugin_download_file, f.UUID), []byte(publicScript(plugins.GetString(f.UUID))), 0666)
				os.WriteFile(plugin_path+"list.json", utils.JsonMarshal(GetPublicResponse()), 0666)
			}()
		}
		return
	})
}

func initPlugin(data string, uuid string) (*common.Function, error) {
	var err error
	var rules []string
	var imType *common.Filter
	var userId *common.Filter
	var groupId *common.Filter
	var cron string
	var admin bool
	var disable bool
	var priority int
	var title string
	var public bool
	var description string
	var icon string
	var version string = "v1.0.0"
	var author string
	var create_at string
	var module bool
	var web bool
	var encrypt bool
	var onStart bool
	var origin = "自定义"
	ress := regexp.MustCompile(
		`\*\s?@([\d\w+-]+)\s+([^\n]+?)\n`,
	).FindAllStringSubmatch(data, -1)
	for _, res := range ress {
		switch res[1] {
		case "rule", "match", "regex", "pattern":
			rule := strings.TrimSpace(res[2])
			_rs := []string{}
		FR:
			ress := regexp.MustCompile(`\[([^\s\[\]]+)\]`).FindAllStringSubmatch(rule, -1)
			if len(ress) != 0 {
				res := ress[len(ress)-1]
				var inner = res[1]
				slice := strings.SplitN(inner, ":", 2)
				name := slice[0]
				ps := ""
				if len(slice) == 2 {
					ps = slice[1]
				}
				if strings.HasSuffix(name, "?") {
					name = strings.TrimRight(name, "?")
					rep := ""
					if ps == "" {
						rep = fmt.Sprintf("[%s]", name)
					} else {
						rep = fmt.Sprintf("[%s:%s]", name, ps)
					}
					for l := range _rs {
						_rs[l] = strings.Replace(_rs[l], res[0], rep, 1)
					}
					rule1 := strings.Replace(rule, res[0], rep, 1)
					if len(_rs) == 0 {
						_rs = append(_rs, rule1)
					}
					rule = strings.Replace(rule, res[0], "", 1)
					rule = regexp.MustCompile("\x20{2,}").ReplaceAllString(rule, " ")
					rule = strings.TrimSpace(rule)
					_rs = append(_rs, rule)
					goto FR
				}
			}
			if len(_rs) != 0 {
				rules = append(rules, _rs...)
			} else {
				rules = append(rules, rule)
			}
		case "platform", "imType", "platform+", "imType+":
			var item []string
			for _, i := range regexp.MustCompile(`[\d\w-]+`).FindAllString(res[2], -1) {
				item = append(item, strings.TrimSpace(i))
			}
			imType = &common.Filter{
				BlackMode: false,
				Items:     item,
			}
		case "platform-", "imType-":
			var item []string
			for _, i := range regexp.MustCompile(`[\d\w-]+`).FindAllString(res[2], -1) {
				item = append(item, strings.TrimSpace(i))
			}
			imType = &common.Filter{
				BlackMode: true,
				Items:     item,
			}
		case "userId", "userID", "uid", "userId+", "userID+", "uid+":
			var item []string
			for _, i := range regexp.MustCompile(`[\d\w-]+`).FindAllString(res[2], -1) {
				item = append(item, strings.TrimSpace(i))
			}
			userId = &common.Filter{
				BlackMode: false,
				Items:     item,
			}
		case "userId-", "userID-", "uid-":
			var item []string
			for _, i := range regexp.MustCompile(`[\d\w-]+`).FindAllString(res[2], -1) {
				item = append(item, strings.TrimSpace(i))
			}
			userId = &common.Filter{
				BlackMode: true,
				Items:     item,
			}
		case "groupId", "groupID", "groupCode", "chat_id", "chat_id+", "chatId", "chatID", "gid", "groupId+", "groupID+", "groupCode+", "chatId+", "chatID+", "gid+":
			var item []string
			for _, i := range regexp.MustCompile(`[\d\w-]+`).FindAllString(res[2], -1) {
				item = append(item, strings.TrimSpace(i))
			}
			groupId = &common.Filter{
				BlackMode: false,
				Items:     item,
			}
		case "groupId-", "groupID-", "groupCode-", "chatId-", "chat_id-", "chatID-", "gid-":
			var item []string
			for _, i := range regexp.MustCompile(`[\d\w-]+`).FindAllString(res[2], -1) {
				item = append(item, strings.TrimSpace(i))
			}
			groupId = &common.Filter{
				BlackMode: true,
				Items:     item,
			}
		case "cron", "crontab":
			cron = strings.TrimSpace(res[2])
			cron = strings.ReplaceAll(cron, `\/`, "/")
		case "admin":
			admin = strings.TrimSpace(res[2]) == "true"
		case "disable":
			disable = strings.TrimSpace(res[2]) == "true"
		case "priority":
			priority = utils.Int(strings.TrimSpace(res[2]))
		case "title", "name", "show":
			title = strings.TrimSpace(res[2])
		case "public":
			public = strings.TrimSpace(res[2]) == "true"
		case "description":
			description = strings.TrimSpace(res[2])
		case "icon":
			icon = strings.TrimSpace(res[2])
		case "version":
			version = strings.TrimSpace(res[2])
		case "author":
			author = strings.TrimSpace(res[2])
		case "create_at":
			create_at = strings.TrimSpace(res[2])
		case "origin":
			origin = strings.TrimSpace(res[2])
		case "module":
			module = strings.TrimSpace(res[2]) == "true"
		case "web":
			web = strings.TrimSpace(res[2]) == "true"
		case "encrypt":
			encrypt = strings.TrimSpace(res[2]) == "true"
		case "on_start":
			onStart = strings.TrimSpace(res[2]) == "true"
		}
	}
	script := ""
	if encrypt {
		script = DecryptPlugin(string(data))
	} else {
		script = string(data)
	}
	script = halfDeEct(script)
	script = strings.ReplaceAll(script, "new Sender", "Sender")
	script = strings.ReplaceAll(script, "new Bucket", "Bucket")
	prg, err2 := goja.Compile(title+".js", script, false)
	if err == nil && err2 != nil {
		err = err2
	}
	if err == nil && len(rules) == 0 && cron != "" {
		err = fmt.Errorf("无效的脚本%s。", title)
	}
	if web {
		onStart = true
	}
	if icon == "" {
		icon = "https://joeschmoe.io/api/v1/random?t=" + fmt.Sprint(time.Now().Nanosecond())
	}
	var running func() bool
	f := &common.Function{
		Handle: func(s common.Sender) interface{} {
			// defer func() {
			// 	err := recover()
			// 	if err != nil {
			// 		console.Error("脚本错误：", err)
			// 		s.Reply(fmt.Sprint(err))
			// 	}
			// }()
			if err2 != nil {
				panic(err2)
			}
			loop := eventloop.NewEventLoop()
			loop.Run(func(vm *goja.Runtime) {
				SetPluginMethod(vm, uuid, onStart)
				ss := &SenderJsIplm{
					Message:    s,
					Vm:         vm,
					Private:    "private",
					Group:      "group",
					Routine:    "routine",
					Persistent: "persistent",
					UUID:       uuid,
				}
				vm.Set("sender", ss)
				vm.Set("s", ss)
				vm.Set("InitAdapter", func(plt, botid string) *Factory {
					f := &Factory{
						uuid: uuid,
						vm:   vm,
					}
					f.Init(plt, botid)
					return f
				})
				vm.Set("initAdapter", func(plt, botid string) *Factory {
					f := &Factory{
						uuid: uuid,
						vm:   vm,
					}
					f.Init(plt, botid)
					return f
				})
				vm.Set("GetAdapter", func(plt, botid string) map[string]interface{} {
					adapter, err := GetAdapter(plt, botid)
					errstr := ""
					if err != nil {
						errstr = err.Error()
					}
					return map[string]interface{}{
						"error":   errstr,
						"adapter": adapter,
					}
				})
				vm.Set("getAdapterBotsID", GetAdapterBotsID)
				vm.Set("getAdapterBotPlts", GetAdapterBotPlts)
				vm.Set("running", running)
				vm.Set("uuid", func() string {
					return uuid
				})

				_, err := vm.RunProgram(prg)
				if err != nil {
					console.Error(strings.ReplaceAll(strings.ReplaceAll(err.Error(), "node_modules/", ""), "github.com/dop251/goja_nodejs/require", ""))
				}
			})
			return nil
		},
		Rules:       rules,
		ImType:      imType,
		UserId:      userId,
		GroupId:     groupId,
		Cron:        cron,
		Admin:       admin,
		Priority:    priority,
		Disable:     disable,
		UUID:        uuid,
		Title:       title,
		Public:      public,
		Description: description,
		Icon:        icon,
		Version:     version,
		Author:      author,
		CreateAt:    create_at,
		Module:      module,
		Encrypt:     encrypt,
		OnStart:     onStart,
		Origin:      origin,
		Running:     onStart,
	}
	running = func() bool {
		return f.Running
	}
	return f, err
}

func ChatID(p interface{}) string {
	switch p := p.(type) {
	case int:
		if p == 0 {
			return ""
		} else {
			return fmt.Sprint(p)
		}
	case string:
		return p
	case nil:
		return ""
	default:
		return fmt.Sprint(p)
	}
}
