package impl_localfile

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"sync"
	"time"
)

const (
	con_FILE_SUFFIX = "txt"

	defaultPanicNum       = 10 // 默认支持的连续panic次数
	defaultPanicIntervals = 5  // 秒

	fileMaxSize = 1 << 27 // 128M
)

type fileLog struct {
	logPath string

	// fileDir + fileName 用于判断是否需要切换日志文件
	fileDir  string
	fileName string
	lv       levelType

	msgChan chan logObject

	f *os.File

	// 如果出现未知panic并一直发生，那么应该要真正的panic
	panicNum  int
	panicTime time.Time

	// 当前已经写入的大小
	curWriteSize int64
}

func newLog(logPath string, lv levelType) *fileLog {
	f := &fileLog{
		logPath:  logPath,
		fileDir:  "",
		fileName: "",
		msgChan:  make(chan logObject, 10240),
		lv:       lv,
		f:        nil,
	}

	go f.loop()

	return f
}

func (f *fileLog) loop() {
	defer func() {
		if r := recover(); r != nil {
			// 将错误输出，而不是记录到文件，是因为可能导致死循环
			fmt.Println("log file lv:", f.lv, " err:", r)

			// 超过间隔时间重置
			if time.Since(f.panicTime)/time.Second > defaultPanicIntervals {
				f.panicNum = 0
				f.panicTime = time.Now()
			}

			// 这里处理连续panic 也是防止循环调用形成死循环
			f.panicNum++
			if f.panicNum >= defaultPanicNum {
				panic(r)
			}
		}
		go f.loop()
	}()

	for logItem := range f.msgChan {
		f.writeLog(logItem)
	}
}

func (f *fileLog) writeLog(logObj logObject) {
	// 由于不使用直接 logUtil 调用，logUtilPlus 存在终端打印的控制，所以 logUtil 只作为纯文件日志组件
	// if PrintImportantLog && (logObj.level == warn || logObj.level == _error || logObj.level == fatal) {
	// 	fmt.Println(logObj.logInfo)
	// }

	// 检查是否需要去切换文件或者创建文件
	f.checkFileAndLoop(logObj.level, logObj.ifIncludeHour)

	f.curWriteSize += int64(len(logObj.logInfo))
	_, _ = f.f.WriteString(logObj.logInfo)
}

// 检查文件是否需要切换
func (f *fileLog) checkFileAndLoop(level levelType, ifIncludeHour bool) {
	// 获取当前时间
	now := time.Now()
	fileAbsoluteDirectory := filepath.Join(f.logPath, strconv.Itoa(now.Year()), strconv.Itoa(int(now.Month())))
	fileName := ""

	if ifIncludeHour {
		fileName = fmt.Sprintf("%s.%s.%s", time.Now().Format("2006-01-02-15"), level, con_FILE_SUFFIX)
	} else {
		fileName = fmt.Sprintf("%s.%s.%s", time.Now().Format("2006-01-02"), level, con_FILE_SUFFIX)
	}

	// 说明已经存在， 检查文件大小，并切换
	if f.f != nil && f.fileName == fileName && f.fileDir == fileAbsoluteDirectory {
		if f.curWriteSize < fileMaxSize {
			return
		}

		// 大小超额切换文件
		f.switchFile()
		return
	}

	// 创建文件夹
	if f.fileDir != fileAbsoluteDirectory {
		if err := os.MkdirAll(fileAbsoluteDirectory, os.ModePerm|os.ModeTemporary); err != nil {
			log.Println("make dir all is err :", err)
		}
		f.fileDir = fileAbsoluteDirectory
	}

	// 创建文件
	if f.fileName != fileName {
		f.fileName = fileName
	}

	// 到这里说明要切换了，关闭之前的file
	if f.f != nil {
		_ = f.f.Close()
	}

	// 正常切换文件
	f.createFile()
}

func (f *fileLog) switchFile() {
	// 关闭文件
	_ = f.f.Close()

	// 重命名
	curFileName := filepath.Join(f.fileDir, f.fileName)
	newFileName := filepath.Join(f.fileDir, fmt.Sprintf("%s.%s.%s", f.fileName, time.Now().Format("15-04-05.999"), con_FILE_SUFFIX))
	err := os.Rename(curFileName, newFileName)
	if err != nil {
		log.Println(err)
	}

	// 再次创建文件
	f.createFile()
}

func (f *fileLog) createFile() {
	// 得到最终的文件绝对路径
	fileAbsolutePath := filepath.Join(f.fileDir, f.fileName)
	// 打开文件(如果文件存在就以读写模式打开，并追加写入；如果文件不存在就创建，然后以写模式打开。)
	file, err := os.OpenFile(fileAbsolutePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm|os.ModeTemporary)
	if err != nil {
		log.Println("open file is err :", err)
		return
	}
	f.f = file
	stat, _ := file.Stat()
	f.curWriteSize = stat.Size()
}

func (f *fileLog) SetLogPath(logPath string) {
	f.logPath = logPath
}

// Close 最后等待 3s 钟
func (f *fileLog) Close(wg *sync.WaitGroup, waitFinish bool) {
	ticker := time.NewTicker(3 * time.Second)
	defer func() {
		ticker.Stop()

		if f.f != nil {
			_ = f.f.Close()
		}

		wg.Done()
	}()

	for {
		if !waitFinish || len(f.msgChan) == 0 {
			break
		}

		select {
		case <-ticker.C:
			fmt.Println("wait close log file timeout:3s")
			return
		default:
			// 1ms 写入文件500条，最多等待 15ms 检查一次就行
			time.Sleep(15 * time.Millisecond)
		}
	}
}
