package trace

import (
	"runtime"
	"sync/atomic"
	"time"

	"virjar.com/majora-go/env"
	"virjar.com/majora-go/global"
	"virjar.com/majora-go/log"
	"virjar.com/majora-go/safe"
)

var (
	sessionEventChan  = make(chan *sessionEvent, runtime.GOMAXPROCS(-1)*100)
	ConnectEvent      = "ConnectEvent"
	TransferEvent     = "TransferEvent"
	MajoraSessionName = "MajoraSessionId"
	UpStreamEvent     = "ReadUpStream"
	DisconnectEvent   = "Disconnect"
	sessionIdNop      = "session_id_not_set"
)

func init() {
	safe.SageGo(func() {
		for {
			e := <-sessionEventChan
			if e.Err != nil {
				log.Trace().Errorf("[%s] [%s] [%s] %s error:%+v",
					e.sessionId, e.Timestamp.Format("2006-01-02 15:04:05.000000"), e.EventName, e.Message, e.Err)
			} else {
				log.Trace().Infof("[%s] [%s] [%s] %s",
					e.sessionId, e.Timestamp.Format("2006-01-02 15:04:05.000000"), e.EventName, e.Message)
			}
		}
	})
}

// Event 事件
type Event struct {
	// 发生时间
	Timestamp time.Time

	// 事件名称
	EventName string

	// 事件消息
	Message string

	// 错误，如果存在
	Err error
}

type sessionEvent struct {
	sessionId string
	*Event
}

type Recorder interface {
	RecordEvent(eventName string, message string)

	RecordErrorEvent(eventName string, message string, err error)

	Enable() bool
}

type nopRecorder struct{}

func (n *nopRecorder) RecordEvent(eventName string, message string) {

}

func (n *nopRecorder) RecordErrorEvent(eventName string, message string, err error) {

}

func (n *nopRecorder) Enable() bool {
	return false
}

type recorderImpl struct {
	sessionId string
}

func (r *recorderImpl) RecordEvent(eventName string, message string) {
	r.RecordErrorEvent(eventName, message, nil)
}

func (r *recorderImpl) RecordErrorEvent(eventName string, message string, err error) {
	event := &Event{
		Timestamp: time.Now(),
		EventName: eventName,
		Message:   message,
		Err:       err,
	}
	sessionEvent := &sessionEvent{
		sessionId: r.sessionId,
		Event:     event,
	}

	// 当 trace 日志 channel 超过 90% 时放弃 trace 记录，防止阻塞主业务
	sessionChanCap := cap(sessionEventChan)
	sessionChanLen := len(sessionEventChan)
	if sessionChanLen > sessionChanCap*9/10 {
		log.Run().Errorf("sessionEventChan data to many -> cap:%d len:%d", sessionChanCap, sessionChanLen)
		return
	}
	sessionEventChan <- sessionEvent
}

func (r *recorderImpl) Enable() bool {
	return true
}

var defaultNopRecorder = nopRecorder{}

var slots = make([]int64, 30)

func acquireRecorder(sessionId string) Recorder {
	if global.CurrentEnv == env.Debug {
		return &recorderImpl{sessionId: sessionId}
	}

	now := time.Now()
	slotIndex := now.Minute() / 2
	timeMinute := now.Unix() / 60

	slot := &slots[slotIndex]
	slotTime := atomic.LoadInt64(slot)
	if slotTime == timeMinute {
		return &defaultNopRecorder
	}

	if atomic.CompareAndSwapInt64(slot, slotTime, timeMinute) {
		return &recorderImpl{sessionId: sessionId}
	}

	return &defaultNopRecorder
}

type Session struct {
	Recorder Recorder
}

func NewSession(sessionId string) *Session {
	if len(sessionId) == 0 {
		sessionId = sessionIdNop
	}
	return &Session{
		Recorder: acquireRecorder(sessionId),
	}
}
