package log

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

func NewLogger(level Level, prefix string, depth int, w ...io.Writer) Logger {
	return NewLog(level, w, prefix, depth, 0)
}

func NewFileWriter(prefix, path string) io.WriteCloser {
	return &file{
		Prefix: prefix,
		Path:   path,
	}
}

func ConsoleWith(level Level, dept int) *Log {
	return NewLog(level, []io.Writer{os.Stdout}, "", dept, 0)
}

func Discard() *Log {
	return NewLog(LevelNone, []io.Writer{io.Discard}, "", 0, 0)
}

func Fork(l Logger, subPath, prefix string) Logger {
	return rebuild(l, subPath, prefix, true)
}

func Part(l Logger, subPath, prefix string) Logger {
	return rebuild(l, subPath, prefix, false)
}

func rebuild(l Logger, subPath, prefix string, withMain bool) Logger {
	switch old := l.(type) {
	case *Log:
		pool := make([]io.Writer, 0, len(old.wPool))
		for _, w := range old.wPool {
			if f, o := w.(*file); o {
				if prefix == "" {
					prefix = "log"
				}
				pool = append(pool,
					NewFileWriter(filepath.Base(prefix), filepath.Join(f.Path, subPath)),
				)
				if withMain {
					pool = append(pool, f)
				}
			} else {
				if w == os.Stdout {
					if withMain {
						pool = append(pool, Console())
					}
				} else {
					pool = append(pool, w)
				}
			}
		}
		if len(pool) == 0 {
			return nil
		}
		return NewLog(old.level, pool, prefix, old.depth, old.buf)
	case MultiLogger:
		part := make(MultiLogger, 0, len(old))
		for _, ol := range old {
			if lg, ok := ol.(*Log); ok {
				if ng := rebuild(lg, subPath, prefix, withMain); ng != nil {
					part = append(part, ng)
				}
			} else {
				part = append(part, ol)
			}
		}
		if len(part) == 1 {
			return part[0]
		}
		return part
	default:
		return l
	}
}

func NewSession(l Logger, sessionId string) Logger {
	switch old := l.(type) {
	case *Log:
		return NewLog(old.level, old.wPool, strings.TrimSuffix(old.prefix, " ")+sessionId, old.depth, old.buf)
	case MultiLogger:
		logs := make(MultiLogger, len(old))
		for i, ol := range old {
			logs[i] = NewSession(ol, sessionId)
		}
		return logs
	default:
		return l
	}
}

var consoleLog = ConsoleWith(LevelDebug, 2)

func Console() *Log {
	return consoleLog
}

type Level int

const (
	LevelNone Level = 0
)

const (
	LevelError Level = iota + 1
	LevelWarn
	LevelInfo
	LevelDebug
)

const (
	LevelsError = "[E]"
	LevelsWarn  = "[W]"
	LevelsInfo  = "[I]"
	LevelsDebug = "[D]"
)

const (
	PrintFlags = log.LstdFlags | log.Llongfile
)

const (
	Ext = ".log"
)

const (
	Layout = "2006_01_02"
)

type FileName string

func (f FileName) Prefix() string {
	return string(f)[:strings.Index(string(f), "_")]
}

func (f FileName) Date() string {
	last := strings.LastIndex(string(f), ".")
	idx := len(f) - len(Layout) - len(Ext)
	return string(f)[idx:last]
}

func (f FileName) Ext() string {
	return filepath.Ext(string(f))
}

func (f FileName) String() string { return string(f) }

func buildPrefix(s string) string {
	return "[" + s + "]"
}

func spitPrefix(s string) string {
	idx := strings.Index(s, " ")
	if idx == -1 {
		return s
	}
	s = strings.ToLower(s[:idx])
	s = strings.TrimPrefix(s, "[")
	s = strings.TrimSuffix(s, "]")
	return s
}

type file struct {
	Prefix string    // svc
	Path   string    // /var/log
	date   time.Time // 2006_01_02
	fi     *os.File
}

func (f *file) Write(b []byte) (n int, err error) {
	if err = f.check(); err != nil {
		return 0, err
	}
	return f.fi.Write(b)
}

func (f *file) Close() error {
	if f.fi != nil {
		return f.fi.Close()
	}
	return nil
}

func (f *file) createDir() error {
	if _, err := os.Stat(f.Path); err != nil {
		if os.IsNotExist(err) {
			// 这里文件夹权限即使设置为 ModePerm, Linux 系统权限也是 755
			if err = os.MkdirAll(f.Path, os.ModePerm); err != nil {
				return err
			}
		}
		return err
	}
	return nil
}

func (f *file) openFile(date string) (*os.File, error) {
	return os.OpenFile(f.name(date), os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModePerm) // 创建文件
}

func (f *file) name(date string) string {
	path := fmt.Sprintf("%s_%s%s", f.Prefix, date, Ext)
	if f.Prefix == "" {
		path = date + Ext
	}
	// /var/log/svc_2006_01_02.log
	return filepath.Join(f.Path, path)
}

func (f *file) checkDate(cur time.Time) bool {
	curY, curM, curD := cur.Date()
	oldY, oldM, oldD := f.date.Date()
	if curY == oldY && curM == oldM && curD == oldD {
		return true
	}
	return false
}

func (f *file) check() error {
	if f.fi == nil {
		if err := f.createDir(); err != nil {
			return err
		}
	}
	cur := time.Now()
	if f.checkDate(cur) {
		return nil
	}
	if f.fi != nil {
		_ = f.fi.Close()
	}
	fi, err := f.openFile(cur.Format(Layout))
	if err != nil {
		return err
	}
	f.fi = fi
	f.date = cur
	return nil
}

type Log struct {
	level  Level
	depth  int // 2
	prefix string
	buf    int
	wPool  []io.Writer
	logs   []*log.Logger

	mu sync.Mutex
}

func NewLog(level Level, writers []io.Writer, prefix string, depth int, buf int) *Log {
	if depth < 0 {
		depth = 0
	}
	if prefix != "" {
		prefix = prefix + " "
	}
	l := new(Log)
	l.level = level
	l.prefix = prefix
	l.depth = depth
	l.wPool = writers
	l.buf = buf
	l.logs = make([]*log.Logger, len(l.wPool))
	for i := 0; i < len(l.wPool); i++ {
		w := l.wPool[i]
		if buf > 0 {
			w = bufio.NewWriterSize(w, buf)
		}
		l.logs[i] = log.New(w, prefix, func() int {
			if depth <= 0 {
				return log.LstdFlags
			}
			return PrintFlags
		}())
	}
	return l
}
func (l *Log) CallDepthPlus() {
	l.depth++
}
func (l *Log) CallDepthMinus() {
	l.depth--
}
func (l *Log) Write(b []byte) (int, error) {
	if l.level == LevelNone {
		return len(b), nil
	}
	l.mu.Lock()
	n, err := bytes.NewReader(b).WriteTo(io.MultiWriter(l.wPool...))
	l.mu.Unlock()
	return int(n), err
}

func (l *Log) Prefix(prefix string, f string, v ...any) {
	if l.level == LevelNone {
		return
	}
	l.mu.Lock()
	for _, lg := range l.logs {
		l.setPrefixFmt(lg, prefix)
		_ = lg.Output(l.depth, fmt.Sprintf(l.prefix+f, v...))
	}
	l.mu.Unlock()
}

func (l *Log) Println(f string, v ...any) {
	if l.level == LevelNone {
		return
	}
	l.mu.Lock()
	for _, lg := range l.logs {
		l.setPrefixFmt(lg, "")
		_ = lg.Output(l.depth, fmt.Sprintf(l.prefix+f, v...))
	}
	l.mu.Unlock()
}

// Logger start
func (l *Log) Error(f string, v ...any) {
	if l.level < LevelError {
		return
	}
	l.mu.Lock()
	for _, lg := range l.logs {
		l.setPrefixFmt(lg, LevelsError)
		_ = lg.Output(l.depth, fmt.Sprintf(l.prefix+f, v...))
	}
	l.mu.Unlock()
}

func (l *Log) Warn(f string, v ...any) {
	if l.level < LevelWarn {
		return
	}
	l.mu.Lock()
	for _, lg := range l.logs {
		l.setPrefixFmt(lg, LevelsWarn)
		_ = lg.Output(l.depth, fmt.Sprintf(l.prefix+f, v...))
	}
	l.mu.Unlock()
}

func (l *Log) Info(f string, v ...any) {
	if l.level < LevelInfo {
		return
	}
	l.mu.Lock()
	for _, lg := range l.logs {
		l.setPrefixFmt(lg, LevelsInfo)
		_ = lg.Output(l.depth, fmt.Sprintf(l.prefix+f, v...))
	}
	l.mu.Unlock()
}

func (l *Log) Debug(f string, v ...any) {
	if l.level < LevelDebug {
		return
	}
	l.mu.Lock()
	for _, lg := range l.logs {
		l.setPrefixFmt(lg, LevelsDebug)
		_ = lg.Output(l.depth, fmt.Sprintf(l.prefix+f, v...))
	}
	l.mu.Unlock()
}

// Logger end

func (l *Log) setPrefixFmt(logger *log.Logger, s string) {
	prefix := s + " "
	if logger.Prefix() == prefix {
		return
	}
	logger.SetPrefix(prefix)
}
