package main

import (
	"context"
	"path/filepath"
	"strconv"
	"sync"
	"sync/atomic"
	"time"

	"github.com/pingcap/log"
	"github.com/pingcap/tidb/pkg/config"
	"github.com/pingcap/tidb/pkg/plugin"
	"go.uber.org/zap"
)

var logger atomic.Value

func initLog() (err error) {
	tidbConf := config.GetGlobalConfig()
	conf, auditLogFile := newAuditLogConfig(tidbConf)
	lg, _, err := log.InitLogger(conf)
	if err != nil {
		err = err
		return
	}
	log.Info("Initialize audit log",
		zap.String("filename", auditLogFile),
		zap.Int("max-size", conf.File.MaxSize),
		zap.Int("buffer-size", tidbConf.Instance.PluginAuditLogBufferSize),
		zap.Int("flush-interval", tidbConf.Instance.PluginAuditLogFlushInterval))
	logger.Store(lg)
	return
}

func newAuditLogConfig(tidbConf *config.Config) (*log.Config, string) {
	const filename = "tidb-audit.log" // TODO: Tweak log configuration by TiDB start arguments.
	baseFolder := filepath.Dir(tidbConf.Log.SlowQueryFile)
	auditLogFile := filepath.Join(baseFolder, filename)
	conf := &log.Config{
		Level:   "info",
		Format:  "text",
		Timeout: tidbConf.Log.Timeout,
		File: log.FileLogConfig{
			MaxSize:             int(auditLogMaxSize >> 20),
			Filename:            auditLogFile,
			IsBuffered:          tidbConf.Instance.PluginAuditLogBufferSize > 0,
			BufferSize:          tidbConf.Instance.PluginAuditLogBufferSize,
			BufferFlushInterval: time.Duration(tidbConf.Instance.PluginAuditLogFlushInterval) * time.Second,
		},
	}
	return conf, auditLogFile
}

type logRecord struct {
	columns []zap.Field
}

func normalizeStatements() bool {
	return atomic.LoadInt32(&redactLog) == 1
}

func audit(ctx context.Context, eventClass, eventSubClass string, statusCode int) *logRecord {
	now, id := auditID()
	l := &logRecord{}
	costTime := time.Duration(0)
	if start := ctx.Value(plugin.ExecStartTimeCtxKey); start != nil {
		costTime = time.Since(start.(time.Time))
	}
	l.columns = append(l.columns, []zap.Field{
		zap.String("ID", id),
		zap.Time("TIMESTAMP", now),
		zap.String("EVENT_CLASS", eventClass),
		zap.String("EVENT_SUBCLASS", eventSubClass),
		zap.Int("STATUS_CODE", statusCode),
		zap.Float64("COST_TIME", float64(costTime)/float64(time.Microsecond)),
	}...)
	return l
}

func (l *logRecord) field(f zap.Field) *logRecord {
	l.columns = append(l.columns, f)
	return l
}

func (l *logRecord) fields(fs []zap.Field) *logRecord {
	l.columns = append(l.columns, fs...)
	return l
}

func (l *logRecord) log() {
	logger.Load().(*zap.Logger).Info("", l.columns...)
}

var last struct {
	sync.Mutex
	ts  int64
	seq int
}

func auditID() (t time.Time, id string) {
	last.Lock()
	var seq int
	t = time.Now()
	newTs := t.UnixNano() / int64(time.Second)
	timeMoveBack := false
	if newTs > last.ts {
		seq = 0
		last.seq = 0
		last.ts = newTs
	} else {
		if newTs < last.ts {
			timeMoveBack = true
		}
		last.seq++ // overflow be negative is ok for audit ID.
		seq = last.seq
	}
	last.Unlock()
	if timeMoveBack {
		log.Warn("time is moving backwards")
	}
	id = strconv.FormatInt(newTs, 10) + strconv.Itoa(seq)
	return
}
