package logger_util

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"path"
	"runtime"

	formatter "github.com/antonfisher/nested-logrus-formatter"
	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus"
)

var(//const (
	maxLogFileSize int64= 1024*1024*128
	maxLogFile     = 8
)

func SetLoggerSizeAndNum(size, num int) {
	maxLogFileSize = 1024*1024*int64(size)
	maxLogFile     = num
}

type FileHook struct {
	file      *os.File
	flag      int
	chmod     os.FileMode
	formatter *formatter.Formatter
}

func renameFiles(file string) {
	var oldFileName, newFileName string
	var i int
	for i = maxLogFile-1; i > 0; i-- {
		if i > 1 {
			oldFileName = fmt.Sprintf("%s.%d",file, i-1)
		} else {
			oldFileName = file
		}
		if _, err := os.Stat(oldFileName); err == nil {
			newFileName = fmt.Sprintf("%s.%d", file, i)
			os.Rename(oldFileName, newFileName)
		}
	}
}

func NewFileHook(file string, flag int, chmod os.FileMode) (*FileHook, error) {
	plainFormatter := &formatter.Formatter{
		TimestampFormat: "2006-01-02 15:04:05.000",
		TrimMessages:    true,
		NoColors:        true,
		NoFieldsColors:  true,
		NoFieldsSpace:   true,
		HideKeys:        true,
		FieldsOrder:     []string{"component", "category", "remote_addr"},
		CallerFirst:     true,
		CustomCallerFormatter: func(f *runtime.Frame) string {
			file := path.Base(f.File)
			if len(file) > 25 {
				file = file[len(file)-25:]
			}
			return fmt.Sprintf(" %15s:%04d", file, f.Line)
		},
	}

	logFile, err := os.OpenFile(file, flag, chmod)
	if err != nil {
		fmt.Fprintf(os.Stderr, "unable to write file on filehook %v", err)
		return nil, err
	}

	return &FileHook{logFile, flag, chmod, plainFormatter}, err
}

// Fire event
func (hook *FileHook) Fire(entry *logrus.Entry) error {
	var line string

	info, err := hook.file.Stat()
	if err != nil {
		return err
	}
	if info.Size() > maxLogFileSize {
		fileInfo, err := os.Stat(hook.file.Name())
		if err != nil {
			return err
		}
		if fileInfo.Size() > maxLogFileSize {
			renameFiles(hook.file.Name())
		}
		hook.file.Close()
		if hook.file, err = os.OpenFile(hook.file.Name(), hook.flag, hook.chmod); err != nil {
			fmt.Fprintf(os.Stderr, "unable to write file on filehook %v", err)
			return err
		}
	}

	if plainformat, err := hook.formatter.Format(entry); err != nil {
		log.Printf("Formatter error: %+v", err)
		return err
	} else {
		line = string(plainformat)
	}
	if _, err := hook.file.WriteString(line); err != nil {
		fmt.Fprintf(os.Stderr, "unable to write file on filehook(entry.String)%v", err)
		return err
	}

	return nil
}

func (hook *FileHook) Levels() []logrus.Level {
	return []logrus.Level{
		logrus.PanicLevel,
		logrus.FatalLevel,
		logrus.ErrorLevel,
		logrus.WarnLevel,
		logrus.InfoLevel,
		logrus.DebugLevel,
	}
}

// CustomResponseWriter
type CustomResponseWriter struct {
	gin.ResponseWriter
	body *bytes.Buffer
}

func (w CustomResponseWriter) Write(b []byte) (int, error) {
	w.body.Write(b)
	return w.ResponseWriter.Write(b)
}

func (w CustomResponseWriter) WriteString(s string) (int, error) {
	w.body.WriteString(s)
	return w.ResponseWriter.WriteString(s)
}
//

//The Middleware will write the Gin logs to logrus.
func ginToLogrus(log *logrus.Entry) gin.HandlerFunc {
	return func(c *gin.Context) {
		path := c.Request.URL.Path
		raw := c.Request.URL.RawQuery

		blw := &CustomResponseWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
		c.Writer = blw
		//

		// Process request
		c.Next()

		clientIP := c.ClientIP()
		method := c.Request.Method
		statusCode := c.Writer.Status()
		errorMessage := c.Errors.ByType(gin.ErrorTypePrivate).String()

		if raw != "" {
			path = path + "?" + raw
		}

		log.Infof("| %3d | %15s | %-7s | %s | %s",
			statusCode, clientIP, method, path, errorMessage)
	}
}

//NewGinWithLogrus - returns an Engine instance with the ginToLogrus and Recovery middleware already attached.
func NewGinWithLogrus(log *logrus.Entry) *gin.Engine {
	engine := gin.New()
	engine.Use(ginToLogrus(log), gin.Recovery())
	return engine
}
