package internal

import (
	"os"
	"path/filepath"
	"sync"
	"time"
)

type Cutter struct {
	level        string        // 日志级别
	layout       string        //时间格式
	formats      []string      //自定义参数
	director     string        //日志文件夹
	retentionDay int           //保留天数
	file         *os.File      //文件
	mutex        *sync.RWMutex // 读写锁
}

type CutterOption func(c *Cutter)

// 设置时间格式
func CutterWithLayout(layout string) CutterOption {
	return func(c *Cutter) {
		c.layout = layout
	}
}

// 格式化参数
func CutterWithFormats(format ...string) CutterOption {
	return func(c *Cutter) {
		if len(format) > 0 {
			c.formats = format
		}
	}
}

// NewCutter 创建一个新的 Cutter 实例，用于管理日志文件的切割和保留。
//
// 参数:
//   - directory: 日志文件存储的目录路径。
//   - level: 日志级别，用于标识日志的严重程度。
//   - retentionDay: 日志文件保留的天数，超过该天数的日志文件将被删除。
//   - options: 可选的 CutterOption 函数，用于对 Cutter 实例进行额外的配置。
//
// 返回值:
//   - *Cutter: 返回一个初始化后的 Cutter 实例。
func NewCutter(directory string, level string, retentionDay int, options ...CutterOption) *Cutter {
	// 初始化 Cutter 实例，设置日志级别、目录、保留天数以及互斥锁
	rotate := &Cutter{
		level:        level,
		director:     directory,
		retentionDay: retentionDay,
		mutex:        new(sync.RWMutex),
	}

	// 应用所有传入的 CutterOption 配置函数
	for i := 0; i < len(options); i++ {
		options[i](rotate)
	}

	return rotate
}

// Write 方法将给定的字节数据写入到日志文件中。该方法会确保日志文件的目录存在，并根据配置的格式生成文件名。
// 如果日志文件已经存在，数据将被追加到文件末尾。如果文件不存在，则会创建新文件。
// 该方法还会定期清理超过保留天数的日志文件夹。
//
// 参数:
//   - bytes: 要写入的字节数据。
//
// 返回值:
//   - n: 成功写入的字节数。
//   - err: 如果发生错误，返回错误信息；否则返回 nil。
func (c *Cutter) Write(bytes []byte) (n int, err error) {
	// 加锁以确保并发安全
	c.mutex.Lock()
	defer func() {
		// 在函数结束时关闭文件并释放锁
		if c.file != nil {
			_ = c.file.Close()
			c.file = nil
		}
		c.mutex.Unlock()
	}()

	// 生成日志文件名
	length := len(c.formats)
	values := make([]string, 0, 3+length)
	values = append(values, c.director)
	if c.layout != "" {
		values = append(values, time.Now().Format(c.layout))
	}
	for i := 0; i < length; i++ {
		values = append(values, c.formats[i])
	}
	values = append(values, c.level+".log")
	filename := filepath.Join(values...)

	// 确保日志文件所在的目录存在
	directory := filepath.Dir(filename)
	err = os.MkdirAll(directory, os.ModePerm)
	if err != nil {
		return 0, nil
	}

	// 清理超过保留天数的日志文件夹
	err = removeNDaysFolders(c.director, c.retentionDay)
	if err != nil {
		return 0, err
	}

	// 打开或创建日志文件，并追加写入数据
	c.file, err = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		return 0, err
	}

	// 将数据写入文件并返回写入的字节数
	return c.file.Write(bytes)
}

// Sync 方法用于将当前文件的内容同步到磁盘，确保所有缓冲区的数据都写入磁盘。
// 该方法在调用时会先获取互斥锁，以确保在同步过程中不会有其他操作干扰。
// 如果当前 Cutter 实例中的文件对象不为 nil，则调用文件对象的 Sync 方法进行同步操作。
// 如果文件对象为 nil，则直接返回 nil，表示无需同步。
//
// 返回值:
//   - error: 如果同步过程中发生错误，则返回该错误；否则返回 nil。
func (c *Cutter) Sync() error {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	// 如果文件对象存在，则调用其 Sync 方法进行同步
	if c.file != nil {
		return c.file.Sync()
	}

	// 文件对象不存在，直接返回 nil
	return nil
}

// removeNDaysFolders 删除指定目录下，指定天数前的文件夹
func removeNDaysFolders(dir string, days int) error {
	if days <= 0 {
		return nil
	}
	cutoff := time.Now().AddDate(0, 0, -days)
	return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() && info.ModTime().Before(cutoff) && path != dir {
			err = os.RemoveAll(path)
			if err != nil {
				return err
			}
		}
		return nil
	})
}
