package logging

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"sync"
	"time"
)

// Logger emits structured JSON logs with correlation support.
type Logger struct {
	mu     sync.Mutex
	level  int
	debug  bool
	out    io.Writer
	fields map[string]any
}

const (
	levelDebug = iota
	levelInfo
	levelWarn
	levelError
)

// New creates a structured logger. When debug is true, debug-level entries are emitted.
func New(level string, debug bool, logPath string) *Logger {
	_ = os.MkdirAll(filepath.Dir(logPath), 0o755)
	f, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
	var out io.Writer = os.Stdout
	if err == nil {
		out = io.MultiWriter(os.Stdout, f)
	}

	return &Logger{
		level:  parseLevel(level),
		debug:  debug,
		out:    out,
		fields: make(map[string]any),
	}
}

// With returns a child logger with additional persistent fields (e.g. correlation_id).
func (l *Logger) With(fields map[string]any) *Logger {
	merged := make(map[string]any, len(l.fields)+len(fields))
	for k, v := range l.fields {
		merged[k] = v
	}
	for k, v := range fields {
		merged[k] = v
	}
	return &Logger{level: l.level, debug: l.debug, out: l.out, fields: merged}
}

func (l *Logger) Debug(msg string, fields map[string]any) {
	if l.debug && l.level <= levelDebug {
		l.emit("debug", msg, nil, fields)
	}
}

func (l *Logger) Info(msg string, fields map[string]any) {
	if l.level <= levelInfo {
		l.emit("info", msg, nil, fields)
	}
}

func (l *Logger) Warn(msg string, err error, fields map[string]any) {
	if l.level <= levelWarn {
		l.emit("warn", msg, err, fields)
	}
}

func (l *Logger) Error(msg string, err error, fields map[string]any) {
	l.emit("error", msg, err, fields)
}

func (l *Logger) emit(level, msg string, err error, fields map[string]any) {
	entry := map[string]any{
		"ts":      time.Now().UTC().Format(time.RFC3339Nano),
		"level":   level,
		"service": "world",
		"msg":     msg,
	}
	for k, v := range l.fields {
		entry[k] = v
	}
	for k, v := range fields {
		entry[k] = v
	}
	if err != nil {
		entry["error"] = err.Error()
		if l.debug {
			entry["error_detail"] = fmt.Sprintf("%+v", err)
		}
	}

	l.mu.Lock()
	defer l.mu.Unlock()
	_ = json.NewEncoder(l.out).Encode(entry)
}

func parseLevel(s string) int {
	switch s {
	case "debug":
		return levelDebug
	case "warn", "warning":
		return levelWarn
	case "error":
		return levelError
	default:
		return levelInfo
	}
}
