package logger

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"sync/atomic"
	"time"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

const (
	envBenchTimingLogEnabled    = "DRIVE9_BENCH_TIMING_LOG_ENABLED"
	envDBTraceLogEnabled        = "DRIVE9_DB_TRACE_LOG_ENABLED"
	envDBSlowTraceMS            = "DRIVE9_DB_SLOW_TRACE_MS"
	envOpenPoolTimingLogEnabled = "DRIVE9_OPEN_POOL_TIMING_LOG_ENABLED"
	envOpenPoolTimingSlowMS     = "DRIVE9_OPEN_POOL_TIMING_SLOW_MS"
	envLogLevel                 = "DRIVE9_LOG_LEVEL"
)

const defaultDBSlowTraceThreshold = 300 * time.Millisecond
const defaultOpenPoolTimingSlowThreshold = 500 * time.Millisecond

const (
	benchTimingLogUnknown uint32 = iota
	benchTimingLogDisabled
	benchTimingLogEnabled
)

const (
	dbTraceLogUnknown uint32 = iota
	dbTraceLogDisabled
	dbTraceLogEnabled
)

const (
	openPoolTimingLogUnknown uint32 = iota
	openPoolTimingLogDisabled
	openPoolTimingLogEnabled
)

var benchTimingLogState atomic.Uint32
var dbTraceLogState atomic.Uint32
var openPoolTimingLogState atomic.Uint32
var dbSlowTraceThresholdNS atomic.Int64
var openPoolTimingSlowThresholdNS atomic.Int64

func NewServerLogger() (*zap.Logger, error) {
	cfg := zap.NewProductionConfig()
	rawLevel := strings.TrimSpace(os.Getenv(envLogLevel))
	if rawLevel != "" {
		var level zapcore.Level
		if err := level.UnmarshalText([]byte(strings.ToLower(rawLevel))); err != nil {
			return nil, fmt.Errorf("%s must be one of debug, info, warn, or error: %w", envLogLevel, err)
		}
		if level < zapcore.DebugLevel || level > zapcore.ErrorLevel {
			return nil, fmt.Errorf("%s must be one of debug, info, warn, or error", envLogLevel)
		}
		cfg.Level.SetLevel(level)
	}
	return cfg.Build()
}

func BenchTimingLogEnabled() bool {
	switch benchTimingLogState.Load() {
	case benchTimingLogDisabled:
		return false
	case benchTimingLogEnabled:
		return true
	}

	enabled := envBool(envBenchTimingLogEnabled, false)
	if enabled {
		benchTimingLogState.Store(benchTimingLogEnabled)
		return true
	}
	benchTimingLogState.Store(benchTimingLogDisabled)
	return false
}

func DBTraceLogEnabled() bool {
	switch dbTraceLogState.Load() {
	case dbTraceLogDisabled:
		return false
	case dbTraceLogEnabled:
		return true
	}

	enabled := envBool(envDBTraceLogEnabled, true)
	if enabled {
		dbTraceLogState.Store(dbTraceLogEnabled)
		return true
	}
	dbTraceLogState.Store(dbTraceLogDisabled)
	return false
}

func DBSlowTraceThreshold() time.Duration {
	if value := dbSlowTraceThresholdNS.Load(); value >= 0 {
		return time.Duration(value)
	}

	threshold := envDurationMS(envDBSlowTraceMS, defaultDBSlowTraceThreshold)
	dbSlowTraceThresholdNS.Store(int64(threshold))
	return threshold
}

func OpenPoolTimingLogEnabled() bool {
	switch openPoolTimingLogState.Load() {
	case openPoolTimingLogDisabled:
		return false
	case openPoolTimingLogEnabled:
		return true
	}

	enabled := envBool(envOpenPoolTimingLogEnabled, true)
	if enabled {
		openPoolTimingLogState.Store(openPoolTimingLogEnabled)
		return true
	}
	openPoolTimingLogState.Store(openPoolTimingLogDisabled)
	return false
}

func OpenPoolTimingSlowThreshold() time.Duration {
	if value := openPoolTimingSlowThresholdNS.Load(); value >= 0 {
		return time.Duration(value)
	}

	threshold := envDurationMS(envOpenPoolTimingSlowMS, defaultOpenPoolTimingSlowThreshold)
	openPoolTimingSlowThresholdNS.Store(int64(threshold))
	return threshold
}

func envBool(key string, fallback bool) bool {
	raw := os.Getenv(key)
	if raw == "" {
		return fallback
	}
	v, err := strconv.ParseBool(raw)
	if err != nil {
		return fallback
	}
	return v
}

func envDurationMS(key string, fallback time.Duration) time.Duration {
	raw := os.Getenv(key)
	if raw == "" {
		return fallback
	}
	v, err := strconv.Atoi(raw)
	if err != nil || v < 0 {
		return fallback
	}
	return time.Duration(v) * time.Millisecond
}

func resetBenchTimingLogEnabledForTest() {
	benchTimingLogState.Store(benchTimingLogUnknown)
}

func resetDBTraceLogEnabledForTest() {
	dbTraceLogState.Store(dbTraceLogUnknown)
}

func resetDBSlowTraceThresholdForTest() {
	dbSlowTraceThresholdNS.Store(-1)
}

func resetOpenPoolTimingLogEnabledForTest() {
	openPoolTimingLogState.Store(openPoolTimingLogUnknown)
}

func resetOpenPoolTimingSlowThresholdForTest() {
	openPoolTimingSlowThresholdNS.Store(-1)
}

// ResetBenchTimingLogEnabledForTest clears the cached benchmark timing flag.
// It is intended for tests in packages outside logger that need to toggle
// DRIVE9_BENCH_TIMING_LOG_ENABLED deterministically.
func ResetBenchTimingLogEnabledForTest() {
	resetBenchTimingLogEnabledForTest()
}

// ResetDBTraceLogEnabledForTest clears the cached DB trace flag.
// It is intended for tests in packages outside logger that need to toggle
// DRIVE9_DB_TRACE_LOG_ENABLED deterministically.
func ResetDBTraceLogEnabledForTest() {
	resetDBTraceLogEnabledForTest()
}

// ResetDBSlowTraceThresholdForTest clears the cached DB slow trace threshold.
// It is intended for tests in packages outside logger that need to toggle
// DRIVE9_DB_SLOW_TRACE_MS deterministically.
func ResetDBSlowTraceThresholdForTest() {
	resetDBSlowTraceThresholdForTest()
}

// ResetOpenPoolTimingLogEnabledForTest clears the cached open-pool timing flag.
// It is intended for tests in packages outside logger that need to toggle
// DRIVE9_OPEN_POOL_TIMING_LOG_ENABLED deterministically.
func ResetOpenPoolTimingLogEnabledForTest() {
	resetOpenPoolTimingLogEnabledForTest()
}

// ResetOpenPoolTimingSlowThresholdForTest clears the cached open-pool timing
// slow threshold. It is intended for tests in packages outside logger that need
// to toggle DRIVE9_OPEN_POOL_TIMING_SLOW_MS deterministically.
func ResetOpenPoolTimingSlowThresholdForTest() {
	resetOpenPoolTimingSlowThresholdForTest()
}

func init() {
	resetDBSlowTraceThresholdForTest()
	resetOpenPoolTimingSlowThresholdForTest()
}
