// Package toolbox provides small shared helpers, mirroring email_server/tool_box.py:
// logging, the configured-timezone clock, and Message-ID generation.
package toolbox

import (
	"crypto/rand"
	"fmt"
	"log"
	"math/big"
	"os"
	"strings"
	"time"

	"gopkg.in/ini.v1"
)

// Logger is a tiny leveled logger matching the Python format:
// "%(asctime)s - %(name)s - %(levelname)s - %(message)s".
type Logger struct {
	name  string
	level Level
	out   *log.Logger
}

type Level int

const (
	LevelDebug Level = iota
	LevelInfo
	LevelWarning
	LevelError
	LevelCritical
)

func parseLevel(s string) Level {
	switch strings.ToUpper(strings.TrimSpace(s)) {
	case "DEBUG":
		return LevelDebug
	case "WARNING", "WARN":
		return LevelWarning
	case "ERROR":
		return LevelError
	case "CRITICAL":
		return LevelCritical
	default:
		return LevelInfo
	}
}

func (l Level) String() string {
	switch l {
	case LevelDebug:
		return "DEBUG"
	case LevelWarning:
		return "WARNING"
	case LevelError:
		return "ERROR"
	case LevelCritical:
		return "CRITICAL"
	default:
		return "INFO"
	}
}

var globalLevel = LevelInfo

// Configure sets the process-wide log level from settings.ini's [Logging] section,
// mirroring tool_box.setup_logging.
func Configure(cfg *ini.File) {
	section := cfg.Section("Logging")
	globalLevel = parseLevel(section.Key("LOG_LEVEL").MustString("INFO"))
}

// GetLogger returns a Logger for the given component name, mirroring tool_box.get_logger.
// Python derives the name from the caller's filename when omitted; Go callers pass it
// explicitly instead, since introspecting the caller module isn't idiomatic here.
func GetLogger(name string) *Logger {
	return &Logger{name: name, level: globalLevel, out: log.New(os.Stderr, "", 0)}
}

func (l *Logger) log(level Level, format string, args ...any) {
	if level < globalLevel {
		return
	}
	msg := fmt.Sprintf(format, args...)
	ts := time.Now().Format("2006-01-02 15:04:05,000")
	l.out.Printf("%s - %s - %s - %s", ts, l.name, level, msg)
}

func (l *Logger) Debug(format string, args ...any)    { l.log(LevelDebug, format, args...) }
func (l *Logger) Info(format string, args ...any)     { l.log(LevelInfo, format, args...) }
func (l *Logger) Warning(format string, args ...any)  { l.log(LevelWarning, format, args...) }
func (l *Logger) Error(format string, args ...any)    { l.log(LevelError, format, args...) }
func (l *Logger) Critical(format string, args ...any) { l.log(LevelCritical, format, args...) }

// EnsureFolderExists creates the parent directory of filepath (a file path, not a
// directory path), mirroring tool_box.ensure_folder_exists including its handling of
// "sqlite:///" prefixed database URLs.
func EnsureFolderExists(path string) error {
	path = strings.TrimPrefix(path, "sqlite:///")
	dir := path
	if idx := strings.LastIndexAny(path, "/\\"); idx >= 0 {
		dir = path[:idx]
	} else {
		return nil
	}
	if dir == "" {
		return nil
	}
	return os.MkdirAll(dir, 0o755)
}

// GetCurrentTime returns the current time in the configured server timezone, mirroring
// tool_box.get_current_time. Falls back to UTC if the configured zone can't be loaded.
func GetCurrentTime(cfg *ini.File) time.Time {
	tzName := cfg.Section("Server").Key("time_zone").MustString("UTC")
	loc, err := time.LoadLocation(tzName)
	if err != nil {
		loc = time.UTC
	}
	return time.Now().In(loc)
}

// GenerateMessageID builds a Message-ID local-part@hostname string, mirroring
// tool_box.generate_message_id: system wall-clock time (not the configured timezone)
// plus a 6-digit random suffix.
func GenerateMessageID(hostname string) string {
	digits := make([]byte, 6)
	for i := range digits {
		n, _ := rand.Int(rand.Reader, big.NewInt(10))
		digits[i] = byte('0' + n.Int64())
	}
	return fmt.Sprintf("%s.%s@%s", time.Now().Format("20060102150405"), string(digits), hostname)
}
