package logger

// |@@| C

import (
	"context"
	"fmt"
	"gardening/src/lib/config"
	"gardening/src/lib/contexter"
	"strconv"
	"strings"
)

type Logger struct {
	Config    *config.Config
	Contexter *contexter.Contexter
	Data      struct {
		Level     string
		Profiling bool
		Tracing   bool
		Profiles  []*Profile
	}
}

type Profile struct {
	Uid  string `json:"uid"`
	Logs []*Log `json:"logs"`
}

const LogLevelDebug = "debug"
const LogLevelInfo = "info"
const LogLevelWarn = "warn"
const LogLevelError = "error"

type Log struct {
	Text      string
	Ctx       string
	Level     string
	Timestamp string
	Kvts      []KVT
	Traced    bool
}

type KVT struct {
	Key   string
	Value string
	Tags  []string
}

func (l *Log) KV(key string, value interface{}) *Log {
	v := l.castToString(value, key)
	kvt := KVT{
		Key:   key,
		Value: v,
	}
	l.Kvts = append(l.Kvts, kvt)
	return l
}

func (l *Log) KVT(key string, value interface{}, tags ...string) *Log {
	v := l.castToString(value, key)
	kvt := KVT{
		Key:   key,
		Value: v,
		Tags:  tags,
	}
	l.Kvts = append(l.Kvts, kvt)
	return l
}

func (this *Logger) N(text string) *Log {
	return &Log{
		Text: text,
		Kvts: nil,
	}
}

func (this *Logger) EnableTracing(ctx context.Context) {
	this.Data.Tracing = true
}

func (this *Logger) DisableTracing(ctx context.Context) {
	this.Data.Tracing = false
}

func (this *Logger) SetLevel(ctx context.Context, level string) {
	this.Data.Level = level
}

func (this *Logger) DebugL(ctx context.Context, log *Log) {
	if !this.shouldLog(LogLevelDebug) {
		return
	}
	this.fillLog(ctx, log, LogLevelDebug)
	this.do(ctx, log)
}

func (this *Logger) do(ctx context.Context, log *Log) {
	if this.Data.Profiling {
		this.profile(ctx, log)
	}
	this.sendLog(ctx, log)
}

func (this *Logger) profile(ctx context.Context, log *Log) {
	uid := this.Contexter.MayGetUid(ctx)
	if uid == "" {
		return
	}
	profile := this.getOrCreateProfile(uid)
	profile.Logs = append(profile.Logs, log)
}

func (this *Logger) GetProfileByUid(ctx context.Context, uid string) *Profile {
	return this.getOrCreateProfile(uid)
}

func (this *Logger) FindCurrentProfile(ctx context.Context) (profile *Profile, has bool) {
	uid := this.Contexter.MayGetUid(ctx)
	if uid == "" {
		return nil, false
	}
	return this.GetProfileByUid(ctx, uid), true
}

func (this *Logger) getOrCreateProfile(uid string) *Profile {
	for _, record := range this.Data.Profiles {
		if record.Uid == uid {
			return record
		}
	}
	record := &Profile{
		Uid: uid,
	}
	this.Data.Profiles = append(this.Data.Profiles, record)
	return record
}

// LogRoute Deprecated
func (this *Logger) LogRoute(ctx context.Context) {}

func (this *Logger) Audit(ctx context.Context, log *Log) {
	// Audit strategy commented for now, will rethink it later

	//b, _ := json.Marshal(log)
	//fileName := this.Config.GetAuditLogsPath()
	//
	//lineToAdd := string(b) + "\n"
	//
	//// Open the file in append mode, create it if it doesn't exist, with write-only permissions
	//file, err := os.OpenFile(fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	//if err != nil {
	//	this.Warn(ctx, "Cannot write audit, cannot open file "+fileName)
	//	return
	//}
	//defer file.Close()
	//
	//if _, err := file.WriteString(lineToAdd); err != nil {
	//	panic("Cannot write audit")
	//}
}

func (this *Logger) sendLog(ctx context.Context, log *Log) {
	color := this.makeLogColor(log)

	level := this.color(color, "["+log.Level+"]")

	s := level + " " + log.Text
	if len(log.Kvts) > 0 {
		s += " " + this.kvtString(log)
	}
	fmt.Println(s)
}

func (this *Logger) fillLog(ctx context.Context, log *Log, level string) {
	uid := ctx.Value("uid")
	if uid != nil {
		log.Ctx = uid.(string)
	}
	log.Level = level
	log.Traced = this.Data.Tracing
}

func (this *Logger) shouldLog(kind string) bool {
	return this.getLogPriority(kind) >= this.getLogPriority(this.Data.Level)
}

func (this *Logger) getLogPriority(kind string) int {
	switch kind {
	case LogLevelDebug:
		return 0
	case LogLevelInfo:
		return 1
	case LogLevelWarn:
		return 2
	case LogLevelError:
		return 3
	default:
		return 1
	}
}

func (this *Logger) Debug(ctx context.Context, message string, args ...interface{}) {
	if !this.shouldLog(LogLevelDebug) {
		return
	}
	this.log(ctx, LogLevelDebug, message, args)
}

func (this *Logger) Warn(ctx context.Context, message string, args ...interface{}) {
	if !this.shouldLog(LogLevelWarn) {
		return
	}
	this.log(ctx, LogLevelWarn, message, args)
}

func (this *Logger) Error(ctx context.Context, message string, args ...interface{}) {
	if !this.shouldLog(LogLevelError) {
		return
	}
	this.log(ctx, LogLevelError, message, args)
}

func (this *Logger) Info(ctx context.Context, message string, args ...interface{}) {
	if !this.shouldLog(LogLevelInfo) {
		return
	}
	this.log(ctx, LogLevelInfo, message, args)
}

func (this *Logger) log(ctx context.Context, level string, message string, args []interface{}) {
	log := this.N(message)
	this.fillLog(ctx, log, level)
	this.fillArgs(log, args)
	this.do(ctx, log)
}

func (this *Logger) fillArgs(log *Log, args []interface{}) {
	for i, arg := range args {
		if i%2 == 1 {
			continue
		}
		k, ok := arg.(string)
		if !ok {
			panic("Invalid logging arguments")
		}
		v := args[i+1]
		log.KV(k, v)
	}
}

func displayKVTs(ss []string, e *Log) []string {
	for _, kvt := range e.Kvts {
		v := strings.ReplaceAll(kvt.Value, "\n", "")
		ss = append(ss, "    - "+kvt.Key+":"+v)
		if len(kvt.Tags) > 0 {
			ss = append(ss, "     ["+strings.Join(kvt.Tags, "|")+"]")
		}
	}
	return ss
}

func (l *Log) castToString(value interface{}, key string) string {
	c, ok := value.(string)
	if ok {
		return c
	}
	c2, ok := value.(int)
	if ok {
		return strconv.Itoa(c2)
	}
	c3, ok := value.(bool)
	if ok {
		if c3 {
			return "true"
		}
		if !c3 {
			return "false"
		}
	}
	panic("Cannot cast into string key " + key)
}

const (
	// Base colors
	Reset = "\033[0m"
	Bold  = "\033[1m"

	// Log level colors
	DebugColor = "\033[36m" // Cyan
	InfoColor  = "\033[34m" // Blue
	WarnColor  = "\033[33m" // Yellow
	ErrorColor = "\033[31m" // Red

	// Additional colors for future use
	Green   = "\033[92m"
	Magenta = "\033[95m"
	Purple  = "\033[35m"
	Pink    = "\033[38;5;206m"
	White   = "\033[97m"
	Gray    = "\033[90m"
)

func (this *Logger) color(color string, string string) string {
	return color + string + Reset
}

func (this *Logger) makeLogColor(log *Log) string {
	switch log.Level {
	case LogLevelDebug:
		return DebugColor
	case LogLevelInfo:
		return InfoColor
	case LogLevelWarn:
		return WarnColor
	case LogLevelError:
		return ErrorColor
	default:
		return ""
	}
}

func (this *Logger) kvtString(log *Log) string {
	var ss []string
	for _, kvt := range log.Kvts {
		s := kvt.Key + "=" + log.castToString(kvt.Value, kvt.Key)
		ss = append(ss, s)
	}
	return strings.Join(ss, " ")
}
