// Package cron
// @Link  https://github.com/bufanyun/hotgo
// @Copyright  Copyright (c) 2023 HotGo CLI
// @Author  Ms <133814250@qq.com>
// @License  https://github.com/bufanyun/hotgo/blob/master/LICENSE
package cron

import (
	"bufio"
	"context"
	"fmt"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gcron"
	"github.com/gogf/gf/v2/os/gctx"
	"github.com/gogf/gf/v2/os/gfile"
	"github.com/gogf/gf/v2/os/glog"
	"github.com/gogf/gf/v2/os/gtime"
	"hotgo/internal/consts"
	"hotgo/internal/dao"
	"hotgo/internal/model/entity"
	"hotgo/utility/simple"
	"os"
	"strings"
	"sync"
)

var crons = &cronManager{
	tasks:   make(map[string]*TaskItem),
	loggers: make(map[string]*glog.Logger),
}

// Cron 定时任务接口
type Cron interface {
	// GetName 获取任务名称
	GetName() string
	// Execute 执行任务
	Execute(ctx context.Context, parser *Parser) (err error)
}

// Parser 任务执行参数
type Parser struct {
	Args   []string     // 任务参数
	Logger *glog.Logger // 日志管理实例
}

// Log 任务调度日志
type Log struct {
	FileName   string `json:"fileName" dc:"文件名称"`
	SizeFormat string `json:"sizeFormat" dc:"文件大小"`
	Contents   string `json:"contents" dc:"文件内容"`
}

// consumerManager 任务管理者
type cronManager struct {
	tasks   map[string]*TaskItem
	loggers map[string]*glog.Logger
	sync.RWMutex
}

type TaskItem struct {
	Pattern string        // 表达式，参考：https://goframe.org/pages/viewpage.action?pageId=30736411
	Name    string        // 唯一的任务名称
	Params  string        // 函数参数，多个用,隔开
	Fun     gcron.JobFunc // 执行的函数接口
	Policy  int64         // 策略 1：并行 2：单例 3：单次 4：多次
	Count   int           // 执行次数，仅Policy=4时有效
}

func Logger() *glog.Logger {
	return g.Log("cron")
}

// Register 注册任务
func Register(c Cron) {
	crons.Lock()
	defer crons.Unlock()

	name := c.GetName()
	if _, ok := crons.tasks[name]; ok {
		Logger().Debugf(gctx.GetInitCtx(), "cron.Register name:%v duplicate registration.", name)
		return
	}

	crons.tasks[name] = &TaskItem{Name: c.GetName(), Fun: GenExecuteFun(c.Execute)}
}

// StopALL 停止所有任务
func StopALL() {
	for _, v := range gcron.Entries() {
		gcron.Remove(v.Name)
	}
}

// StartALL 启动所有任务
func StartALL(sysCron []*entity.SysCron) (err error) {
	crons.Lock()
	defer crons.Unlock()

	if len(crons.tasks) == 0 {
		g.Log().Debug(gctx.GetInitCtx(), "no scheduled task is available.")
		return
	}

	for _, cron := range sysCron {
		f, ok := crons.tasks[cron.Name]
		if !ok {
			return gerror.Newf("该任务没有加入任务列表:%v", cron.Name)
		}

		sn := GenCronSn(cron)

		// 没有则添加
		if gcron.Search(sn) == nil {
			var (
				t   *gcron.Entry
				ctx = GenCronCtx(cron)
			)
			switch cron.Policy {
			case consts.CronPolicySame:
				t, err = gcron.Add(ctx, cron.Pattern, f.Fun, sn)

			case consts.CronPolicySingle:
				t, err = gcron.AddSingleton(ctx, cron.Pattern, f.Fun, sn)

			case consts.CronPolicyOnce:
				t, err = gcron.AddOnce(ctx, cron.Pattern, f.Fun, sn)

			case consts.CronPolicyTimes:
				if f.Count <= 0 {
					f.Count = 1
				}
				t, err = gcron.AddTimes(ctx, cron.Pattern, int(cron.Count), f.Fun, sn)

			default:
				return gerror.Newf("使用无效的策略, cron.Policy=%v", cron.Policy)
			}

			if err != nil {
				return err
			}
			if t == nil {
				return gerror.New("启动任务失败")
			}
		}

		gcron.Start(sn)

		// 执行完毕，单次和多次执行的任务更新状态
		if cron.Policy == consts.CronPolicyOnce || cron.Policy == consts.CronPolicyTimes {
			if _, err = dao.SysCron.Ctx(gctx.GetInitCtx()).Where(dao.SysCron.Columns().Id, cron.Id).Data(g.Map{dao.SysCron.Columns().Status: consts.StatusDisable, dao.SysCron.Columns().UpdatedAt: gtime.Now()}).Update(); err != nil {
				err = gerror.Wrap(err, "定时任务执行失败！")
				return err
			}
		}
	}

	Logger().Debug(gctx.GetInitCtx(), "load cron success..")
	return nil
}

// RefreshStatus 刷新状态
func RefreshStatus(sysCron *entity.SysCron) (err error) {
	if sysCron == nil {
		return
	}

	if sysCron.Status == consts.StatusEnabled {
		return ResetStart(sysCron)
	}
	return Stop(sysCron)
}

// Stop 停止单个任务
func Stop(sysCron *entity.SysCron) (err error) {
	cr := gcron.Search(GenCronSn(sysCron))
	if cr == nil {
		return
	}
	cr.Stop()
	return
}

// ResetStart 重置任务
func ResetStart(sysCron *entity.SysCron) (err error) {
	if err = Stop(sysCron); err != nil {
		return
	}
	if err = Delete(sysCron); err != nil {
		return
	}
	return Start(sysCron)
}

// Once 立即执行一次某个任务
func Once(ctx context.Context, sysCron *entity.SysCron) error {
	crons.RLock()
	defer crons.RUnlock()

	for _, v := range crons.tasks {
		if v.Name == sysCron.Name {
			simple.SafeGo(ctx, func(ctx context.Context) {
				v.Fun(GenCronCtx(sysCron))
			})
			return nil
		}
	}
	return gerror.Newf("定时任务不存在：%+v", sysCron.Name)
}

// Delete 删除任务
func Delete(sysCron *entity.SysCron) (err error) {
	if sysCron == nil {
		return
	}

	for _, v := range gcron.Entries() {
		if v.Name == GenCronSn(sysCron) {
			gcron.Remove(v.Name)
		}
	}
	return
}

// Start 启动单个任务
func Start(sysCron *entity.SysCron) (err error) {
	if sysCron == nil {
		return
	}

	c := gcron.Search(GenCronSn(sysCron))
	if c != nil {
		c.Start()
		return
	}
	return StartALL([]*entity.SysCron{sysCron})
}

// DispatchLog 查看指定任务的调度日志
func DispatchLog(sysCron *entity.SysCron) (log *Log, err error) {
	path := fmt.Sprintf("%v/%v", Logger().GetConfig().Path, GenCronSn(sysCron))
	file, err := FindLastModifiedFile(path)
	if err != nil {
		return nil, err
	}

	if len(file) == 0 || !gfile.IsFile(file) {
		err = gerror.New("未找到日志！")
		return
	}

	log = new(Log)
	log.FileName = file
	log.SizeFormat = gfile.SizeFormat(file)

	if gfile.Size(file) > 1024*50 {
		log.Contents, err = ReadLastLines(file, 100)
		if err != nil {
			return nil, err
		}
	} else {
		log.Contents = gfile.GetContents(file)
	}
	return
}

func ReadLastLines(filename string, lineCount int) (string, error) {
	file, err := os.Open(filename)
	if err != nil {
		return "", err
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)

	lines := make([]string, 0, lineCount)
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
		if len(lines) > lineCount {
			lines = lines[1:]
		}
	}

	if err = scanner.Err(); err != nil {
		return "", err
	}

	result := strings.Join(lines, "\n")
	return result, nil
}

func FindLastModifiedFile(dirPath string) (string, error) {
	if !gfile.Exists(dirPath) {
		return "", gerror.New("该任务暂未产生日志！")
	}

	files, err := gfile.ScanDir(dirPath, "*.log", true)
	if err != nil {
		return "", err
	}

	var lastModifiedFile string
	var lastModifiedTime int64 = 0

	for _, file := range files {
		modTime := gfile.MTimestamp(file)
		if modTime > lastModifiedTime {
			lastModifiedTime = modTime
			lastModifiedFile = file
		}
	}
	return lastModifiedFile, nil
}
