/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openFuyao is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

package log

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"syscall"
	"testing"
	"time"
)

func TestNewFileLogger(t *testing.T) {
	dir := makeTempDir("TmpFileForTest", t)
	defer os.RemoveAll(dir)
	testFileName := filepath.Join(dir, "test.log")
	l := &FileLogger{
		fileName: testFileName,
	}
	defer l.Close()
	b := []byte("test data")
	n, err := l.Write(b)

	assertIsNil(err, t)
	assertEquals(len(b), n, t)

	checkExistsFileWithSize(testFileName, n, t)
	checkFileCount(dir, 1, t)
}

func TestOpenExistingLog(t *testing.T) {
	dir := makeTempDir("TmpFileForTest", t)
	defer os.RemoveAll(dir)

	testFileName := filepath.Join(dir, "test.log")
	data := []byte("test pre data")
	err := os.WriteFile(testFileName, data, fileModeUserRwForWritingLog)
	assertIsNil(err, t)
	checkExistsFileWithSize(testFileName, len(data), t)

	l := &FileLogger{
		fileName: testFileName,
	}
	defer l.Close()

	b := []byte("test post data")
	n, err := l.Write(b)
	assertIsNil(err, t)
	assertEquals(len(b), n, t)

	checkExistsFileWithSize(testFileName, len(b)+len(data), t)
	checkFileCount(dir, 1, t)
}

func TestWriteInvalidLenLog(t *testing.T) {
	dir := makeTempDir("TmpFileForTest", t)
	defer os.RemoveAll(dir)

	var maxContentLen = 5
	megabyte = 1
	testFileName := filepath.Join(dir, "test.log")
	l := &FileLogger{
		fileName: testFileName,
		maxSize:  maxContentLen,
	}

	data := []byte("efghijklmn")
	_, err2 := l.Write(data)
	assertNotNil(err2, t)
}

var testFileTimeCount = 0

func fakeTimeCurrent() time.Time {
	testFileTimeCount += 1
	return time.Now().Add(time.Minute * time.Duration(testFileTimeCount))
}

func TestAutoRotateCleanAndMaxBackups(t *testing.T) {
	dir := makeTempDir("AutoCompressRotate", t)
	defer os.RemoveAll(dir)

	var maxContentLen = 5
	var maxBackupNum = 2
	const maxBackupFileMultiplier = 2
	megabyte = 1
	testFileName := filepath.Join(dir, "test.log")
	l := &FileLogger{
		fileName:   testFileName,
		maxSize:    maxContentLen,
		maxBackups: maxBackupNum,
	}
	defer l.Close()

	data1 := []byte("abcd")
	data1Len, err1 := l.Write(data1)
	assertIsNil(err1, t)
	assertEquals(len(data1), data1Len, t)
	checkExistsFileWithSize(testFileName, data1Len, t)
	checkFileCount(dir, 1, t)

	const delayCount = 100
	currentTime = fakeTimeCurrent
	data2 := []byte("efgh")
	data2Len, err2 := l.Write(data2)
	assertIsNil(err2, t)
	assertEquals(len(data2), data2Len, t)
	checkExistsFileWithSize(testFileName, data2Len, t)
	<-time.After(time.Millisecond * delayCount)
	checkFileCountRange(dir, maxBackupNum, maxBackupNum+1, t)

	currentTime = fakeTimeCurrent
	data2 = []byte("ijk")
	data2Len, err2 = l.Write(data2)
	assertIsNil(err2, t)
	assertEquals(len(data2), data2Len, t)
	checkExistsFileWithSize(testFileName, data2Len, t)
	<-time.After(time.Millisecond * delayCount)
	checkFileCountRange(dir, maxBackupNum+1, maxBackupNum*maxBackupFileMultiplier+1, t)

	currentTime = fakeTimeCurrent
	data2 = []byte("ijk")
	data2Len, err2 = l.Write(data2)
	assertIsNil(err2, t)
	assertEquals(len(data2), data2Len, t)
	checkExistsFileWithSize(testFileName, data2Len, t)
	<-time.After(time.Millisecond * delayCount)
	checkFileCountRange(dir, maxBackupNum+1, maxBackupNum*maxBackupFileMultiplier+1, t)
}

func makeTempDir(name string, t testing.TB) string {
	dir := time.Now().Format(name)
	dir = filepath.Join(os.TempDir(), dir)
	assertIsNil(os.Mkdir(dir, dirModeUserRwxForLogDir), t)
	return dir
}

func checkExistsFileWithSize(path string, len int, t testing.TB) {
	info, err := os.Stat(path)
	assertIsNil(err, t)
	assertEquals(int64(len), info.Size(), t)
}

func checkFileCount(dir string, expect int, t testing.TB) {
	dirs, err := os.ReadDir(dir)
	t.Logf("--> Check dir[%s] files:", dir)
	for _, f := range dirs {
		t.Logf("  %s\n", f.Name())
	}
	assertIsNil(err, t)
	assertEquals(expect, len(dirs), t)
}

func checkFileCountRange(dir string, min, max int, t testing.TB) {
	dirs, err := os.ReadDir(dir)
	t.Logf("--> Check dir[%s] files:", dir)
	for _, f := range dirs {
		t.Logf("  %s\n", f.Name())
	}
	assertIsNil(err, t)
	if len(dirs) < min || len(dirs) > max {
		t.Errorf("file count out of range, expect [%d, %d], got: %d", min, max, len(dirs))
	}
}

func getFileMode(name string) (os.FileMode, error) {
	var actualMode os.FileMode
	info, err := os.Stat(name)
	if err != nil {
		fmt.Println("Error getting file info:", err)
		return actualMode, err
	}

	actualMode = info.Mode() & os.ModePerm
	return actualMode, nil
}

func getRotateLogFile(dir string) (string, error) {
	files, err := os.ReadDir(dir)
	if err != nil {
		fmt.Println("Error reading directory:", err)
		return "", err
	}

	for _, file := range files {
		if !file.IsDir() && filepath.Ext(file.Name()) == ".gz" {
			return filepath.Join(dir, file.Name()), nil
		}
	}

	return "", errors.New("not found rotate logfile")
}

func TestLogFilePermissions(t *testing.T) {
	syscall.Umask(0)

	dir := "/tmp/TmpFileForPermission"
	fileName := "test.log"
	defer os.RemoveAll(dir)

	var maxContentLen = 5
	megabyte = 1
	testFileName := filepath.Join(dir, fileName)
	l := &FileLogger{
		fileName:   testFileName,
		maxSize:    maxContentLen,
		maxBackups: 1,
	}

	data := []byte("efgh")
	_, err2 := l.Write(data)
	assertIsNil(err2, t)

	modeFile, err := getFileMode(testFileName)
	assertIsNil(err, t)
	assertEquals(os.FileMode(fileModeUserRwForWritingLog), modeFile, t)
	modeDir, err := getFileMode(dir)
	assertIsNil(err, t)
	assertEquals(os.FileMode(dirModeUserRwxForLogDir), modeDir, t)

	data = []byte("efg")
	_, err2 = l.Write(data)
	assertIsNil(err2, t)

	const delayCount = 100
	<-time.After(time.Millisecond * delayCount)

	rotateFile, err := getRotateLogFile(dir)
	assertIsNil(err, t)
	modeRotateFile, err := getFileMode(rotateFile)
	assertIsNil(err, t)
	assertEquals(os.FileMode(fileModeUserRoForArchiveLog), modeRotateFile, t)
}