package logs

import (
	"fmt"
	"io"
	"math/rand"
	"os"
	"path/filepath"
	"sync"
	"time"

	"golib/v1/log"
)

// 操作日志: 做了什么动作
// 安全日志: 登录/修改密码/权限等
// 设备日志: 设备之间的通信及启动等操作, 联网等
// 运行日志: 文本

const (
	Action = "[Action] " // Action 操作日志: 做了什么动作
	Safety = "[Safety] " // Safety 安全日志: 登录/修改密码/权限等
	Device = "[Device] " // Device 设备日志: 设备之间的通信及启动等操作, 联网等

	All = "[All] " // 其他
)

const (
	minNum = 1000000000
	maxNum = 9999999999
)

var (
	sessionId = rand.New(rand.NewSource(time.Now().UnixNano()))
)

type Logs struct {
	id     int64
	closer io.Closer
	log    *log.Logger
}

func (c *Logs) Session() *Logs {
	return &Logs{id: sessionId.Int63n(maxNum-minNum) + minNum, closer: c.closer, log: c.log}
}

// Println 使用此方法打印不会被分析
func (c *Logs) Println(f string, v ...any) {
	if c.id == 0 {
		c.log.Print(fmt.Sprintf(f, v...))
		return
	}
	c.log.Print(c.id, " ", fmt.Sprintf(f, v...))
}

// Action 操作日志
func (c *Logs) Action(f string, v ...any) {
	if c.id == 0 {
		c.log.Print(Action, fmt.Sprintf(f, v...))
		return
	}
	c.log.Print(c.id, " ", Action, fmt.Sprintf(f, v...))
}

// Safety 安全日志
func (c *Logs) Safety(f string, v ...any) {
	if c.id == 0 {
		c.log.Print(Safety, fmt.Sprintf(f, v...))
		return
	}
	c.log.Print(c.id, " ", Safety, fmt.Sprintf(f, v...))
}

// Device 设备日志
func (c *Logs) Device(f string, v ...any) {
	if c.id == 0 {
		c.log.Print(Device, fmt.Sprintf(f, v...))
		return
	}
	c.log.Print(c.id, " ", Device, fmt.Sprintf(f, v...))
}

// Close 详情见 ../writer.go 内的 Close 方法
func (c *Logs) Close() error {
	return c.closer.Close()
}

const (
	DefaultSuffix = ".log"
)

type Manager struct {
	pre  string
	path string
	idx  map[string]*Logs
	mu   sync.Mutex
}

func (m *Manager) Get(id string) (*Logs, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	if logs, ok := m.idx[id]; ok {
		return logs, nil
	}

	out, err := log.NewWriter(m.pre, DefaultSuffix, m.path)
	if err != nil {
		return nil, err
	}

	logs := &Logs{
		log:    log.New(out, id+" ", log.LstdFlags),
		closer: out,
	}

	m.idx[id] = logs
	return logs, nil
}

// NewManager 创建日志管理器
// 当一个文件被多次打开时, 会创建多个 socket, 当并发写入时会导致安全隐患
// Manager 可以在多次打开文件始终返回同一个文件句柄
func NewManager(prefix string, path ...string) *Manager {
	return &Manager{
		pre:  prefix,
		path: filepath.Join(path...),
		idx:  make(map[string]*Logs, 256),
	}
}

// NewStdout 默认输出到控制台, 通常在整体代码未初始化时作为默认值使用
func NewStdout(id string) *Logs {
	logs := &Logs{
		log:    log.New(os.Stdout, id+" ", log.LstdFlags),
		closer: os.Stdout,
	}
	return logs
}
