// Copyright © 2023 OpenIM open source community. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package log

import (
	"bufio"
	"io"
	"os"
	"time"

	nested "github.com/antonfisher/nested-logrus-formatter"
	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
	"github.com/rifflock/lfshook"
	"github.com/sirupsen/logrus"

	"github.com/openimsdk/openkf/server/internal/config"
)

var _logger *logrus.Logger

// InitLogger init logger.
func InitLogger() {
	_logger = loggerInit()
}

func loggerInit() *logrus.Logger {
	logger := logrus.New()

	// set log level
	if config.Config.App.Debug {
		logger.SetLevel(logrus.DebugLevel)
	} else {
		logger.SetLevel(logrus.InfoLevel)
	}

	// set output
	src, err := os.OpenFile(os.DevNull, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
	if err != nil {
		panic(err.Error())
	}
	writer := bufio.NewWriter(src)

	// write to stdout and file
	logger.SetOutput(io.MultiWriter(os.Stdout, writer))

	// set formatter
	logger.SetFormatter(&nested.Formatter{
		TimestampFormat: "2006-01-02 15:04:05.000",
		HideKeys:        false,
		FieldsOrder:     []string{"PID", "FilePath", "OperationID"},
	})

	// add print fileline hook
	logger.AddHook(newFilelineHook())

	// add rotate file hook, default is 24h
	logger.AddHook(NewLfsHook(24*time.Hour, 7))

	return logger
}

// NewLfsHook add fileline hook.
func NewLfsHook(rotationTime time.Duration, maxRemainNum uint) logrus.Hook {
	lfsHook := lfshook.NewHook(lfshook.WriterMap{
		logrus.DebugLevel: initRotateLogs(rotationTime, maxRemainNum),
		logrus.InfoLevel:  initRotateLogs(rotationTime, maxRemainNum),
		logrus.WarnLevel:  initRotateLogs(rotationTime, maxRemainNum),
		logrus.ErrorLevel: initRotateLogs(rotationTime, maxRemainNum),
	}, &nested.Formatter{
		TimestampFormat: "2006-01-02 15:04:05.000",
		HideKeys:        false,
		FieldsOrder:     []string{"PID", "FilePath", "OperationID"},
	})

	return lfsHook
}

func initRotateLogs(rotationTime time.Duration, maxRemainNum uint) *rotatelogs.RotateLogs {
	writer, err := rotatelogs.New(
		config.Config.App.LogFile+".%Y%m%d%H%M",
		rotatelogs.WithRotationTime(rotationTime),
		rotatelogs.WithRotationCount(maxRemainNum),
	)
	if err != nil {
		panic(err.Error())
	} else {
		return writer
	}
}

// GetLogger get logger instance.
func GetLogger() *logrus.Logger {
	return _logger
}

// Info log info.
func Info(Operation string, args ...interface{}) {
	_logger.WithFields(logrus.Fields{
		"Operation": Operation,
	}).Infoln(args...)
}

// Error log error.
func Error(Operation string, args ...interface{}) {
	_logger.WithFields(logrus.Fields{
		"Operation": Operation,
	}).Errorln(args...)
}

// Debug log debug.
func Debug(Operation string, args ...interface{}) {
	_logger.WithFields(logrus.Fields{
		"Operation": Operation,
	}).Debugln(args...)
}

// Panic log panic.
func Panic(Operation string, args ...interface{}) {
	_logger.WithFields(logrus.Fields{
		"Operation": Operation,
	}).Panicln(args...)
}

// Infof log info with format.
func Infof(Operation string, format string, args ...interface{}) {
	_logger.WithFields(logrus.Fields{
		"Operation": Operation,
	}).Infof(format, args...)
}

// Errorf log error with format.
func Errorf(Operation string, format string, args ...interface{}) {
	_logger.WithFields(logrus.Fields{
		"Operation": Operation,
	}).Errorf(format, args...)
}

// Debugf log debug with format.
func Debugf(Operation string, format string, args ...interface{}) {
	_logger.WithFields(logrus.Fields{
		"Operation": Operation,
	}).Debugf(format, args...)
}

// Panicf log panic with format.
func Panicf(Operation string, format string, args ...interface{}) {
	_logger.WithFields(logrus.Fields{
		"Operation": Operation,
	}).Panicf(format, args...)
}
