package oplog

import (
	"context"
	"github.com/loveuer/nf"
	"github.com/loveuer/nfflow/internal/database"
	"github.com/loveuer/nfflow/internal/model"
	"github.com/loveuer/nfflow/internal/opt"
	"github.com/loveuer/nfflow/internal/sqlType"
	"github.com/loveuer/nfflow/internal/util"
	"github.com/sirupsen/logrus"
	"sync"
	"time"
)

var (
	_once = &sync.Once{}
	lc    = make(chan *model.OpLog, 1024)
)

// NewOpLog
//
// * 记录操作日志的 中间件使用方法如下:
//
//	 app := nf.New()
//	 app.Post("/login", oplog.NewOpLog(ctx), HandleLog)
//
//	 func HandleLog(c *nf.Ctx) error {
//		// 你的操作逻辑
//		c.Local(opt.OpLogLocalKey, &oplog.OpLog{})
//		// 剩下某些逻辑
//		// return xxx
//	 }
func NewOpLog(ctx context.Context) nf.HandlerFunc {

	_once.Do(func() {
		go func() {
			var (
				err    error
				ticker = time.NewTicker(time.Duration(opt.OpLogWriteDurationSecond) * time.Second)
				list   = make([]*model.OpLog, 0, 1024)

				write = func() {
					if len(list) == 0 {
						return
					}

					if err = database.DB.Session(util.Timeout(10)).
						Model(&model.OpLog{}).
						Create(&list).
						Error; err != nil {
						logrus.Errorf("middleware.NewOpLog: write logs err=%v", err)
					}

					list = list[:0]
				}
			)

		Loop:
			for {
				select {
				case <-ctx.Done():
					break Loop
				case <-ticker.C:
					write()
				case item, ok := <-lc:
					if !ok {
						return
					}

					list = append(list, item)

					if len(list) >= 100 {
						write()
					}
				}
			}

			write()
		}()
	})

	return func(c *nf.Ctx) error {
		now := time.Now()

		err := c.Next()

		op, ok := c.Locals("user").(*model.User)

		opv := c.Locals(opt.OpLogLocalKey)
		log, ok := opv.(*OpLog)
		if !ok {
			logrus.Warnf("middleware.NewOpLog: %s - %s local '%s' to [*OpLog] invalid", c.Method(), c.Path(), opt.OpLogLocalKey)
			return err
		}

		log.Content["time"] = now.UnixMilli()
		log.Content["user_id"] = op.Id
		log.Content["username"] = op.Username
		log.Content["created_at"] = now.UnixMilli()

		select {
		case lc <- &model.OpLog{
			CreatedAt: now.UnixMilli(),
			UpdatedAt: now.UnixMilli(),
			UserId:    op.Id,
			Username:  op.Username,
			Type:      log.Type,
			Content:   sqlType.NewJSONB(log.Content),
		}:
		case <-util.Timeout(3).Done():
			logrus.Warnf("middleware.NewOpLog: %s - %s log -> chan timeout[3s]", c.Method, c.Path())
		}

		return err
	}
}