package config

import (
	"fmt"
	"math"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

type Config struct {
	AppName          string
	AppVersion       string
	Environment      string
	Addr             string
	BaseURL          string
	DataDir          string
	AdminToken       string
	StaticDir        string
	TemplateDir      string
	ReadTimeout      time.Duration
	WriteTimeout     time.Duration
	IdleTimeout      time.Duration
	JobsEnabled      bool
	CleanupEnabled   bool
	CleanupEvery     time.Duration
	ThumbnailEnabled bool
	ThumbnailEvery   time.Duration
	MaxUploadSize    int64
	DefaultSettings  SettingsDefaults
}

type SettingsDefaults struct {
	AnonymousUploadsEnabled bool
	AnonymousMaxUploadMB    float64
	AnonymousDailyUploadMB  float64
	UserDailyUploadMB       float64
	DefaultUserStorageMB    float64
	UsageRetentionDays      int
	LocalStorageMaxGB       float64
	AnonymousMaxDays        int
	UserMaxDays             int
	AnonymousDailyBoxes     int
	UserDailyBoxes          int
	AnonymousActiveBoxes    int
	UserActiveBoxes         int
	ShortWindowRequests     int
	ShortWindowSeconds      int
	AnonymousStorageBackend string
	UserStorageBackend      string
}

func Load() (Config, error) {
	cfg := Config{
		AppName:          envString("WARPBOX_APP_NAME", "warpbox.dev"),
		AppVersion:       envString("APP_VERSION", "dev"),
		Environment:      envString("WARPBOX_ENV", "development"),
		Addr:             envString("WARPBOX_ADDR", ":8080"),
		BaseURL:          strings.TrimRight(envString("WARPBOX_BASE_URL", "http://localhost:8080"), "/"),
		DataDir:          envString("WARPBOX_DATA_DIR", defaultPath("data")),
		AdminToken:       envString("WARPBOX_ADMIN_TOKEN", ""),
		StaticDir:        envString("WARPBOX_STATIC_DIR", defaultPath("static")),
		TemplateDir:      envString("WARPBOX_TEMPLATE_DIR", defaultPath("templates")),
		ReadTimeout:      envDuration("WARPBOX_READ_TIMEOUT", 15*time.Second),
		WriteTimeout:     envDuration("WARPBOX_WRITE_TIMEOUT", 60*time.Second),
		IdleTimeout:      envDuration("WARPBOX_IDLE_TIMEOUT", 120*time.Second),
		JobsEnabled:      envBool("WARPBOX_JOBS_ENABLED", true),
		CleanupEnabled:   envBool("WARPBOX_CLEANUP_ENABLED", true),
		CleanupEvery:     envDuration("WARPBOX_CLEANUP_EVERY", time.Hour),
		ThumbnailEnabled: envBool("WARPBOX_THUMBNAIL_ENABLED", true),
		ThumbnailEvery:   envDuration("WARPBOX_THUMBNAIL_EVERY", time.Minute),
		MaxUploadSize:    envMegabytes("WARPBOX_MAX_UPLOAD_SIZE_MB", 2048), // 2 GiB default.
		DefaultSettings: SettingsDefaults{
			AnonymousUploadsEnabled: envBool("WARPBOX_ANONYMOUS_UPLOADS_ENABLED", true),
			AnonymousMaxUploadMB:    envMegabytesLimitFloat("WARPBOX_ANONYMOUS_MAX_UPLOAD_MB", 512),
			AnonymousDailyUploadMB:  envMegabytesLimitFloat("WARPBOX_ANONYMOUS_DAILY_UPLOAD_MB", 2048),
			UserDailyUploadMB:       envMegabytesLimitFloat("WARPBOX_USER_DAILY_UPLOAD_MB", 8192),
			DefaultUserStorageMB:    envMegabytesFloat("WARPBOX_DEFAULT_USER_STORAGE_MB", 51200),
			UsageRetentionDays:      envInt("WARPBOX_USAGE_RETENTION_DAYS", 30),
			LocalStorageMaxGB:       envGigabytesFloat("WARPBOX_LOCAL_STORAGE_MAX_GB", 100),
			AnonymousMaxDays:        envInt("WARPBOX_ANONYMOUS_MAX_DAYS", 30),
			UserMaxDays:             envInt("WARPBOX_USER_MAX_DAYS", 90),
			AnonymousDailyBoxes:     envInt("WARPBOX_ANONYMOUS_DAILY_BOXES", 100),
			UserDailyBoxes:          envInt("WARPBOX_USER_DAILY_BOXES", 250),
			AnonymousActiveBoxes:    envInt("WARPBOX_ANONYMOUS_ACTIVE_BOXES", 500),
			UserActiveBoxes:         envInt("WARPBOX_USER_ACTIVE_BOXES", 1000),
			ShortWindowRequests:     envInt("WARPBOX_SHORT_WINDOW_REQUESTS", 60),
			ShortWindowSeconds:      envInt("WARPBOX_SHORT_WINDOW_SECONDS", 60),
			AnonymousStorageBackend: envString("WARPBOX_ANONYMOUS_STORAGE_BACKEND", "local"),
			UserStorageBackend:      envString("WARPBOX_USER_STORAGE_BACKEND", "local"),
		},
	}

	if cfg.BaseURL == "" {
		return Config{}, fmt.Errorf("WARPBOX_BASE_URL cannot be empty")
	}
	if cfg.MaxUploadSize <= 0 {
		return Config{}, fmt.Errorf("WARPBOX_MAX_UPLOAD_SIZE_MB must be positive")
	}
	if !validUnlimitedMegabyteLimit(cfg.DefaultSettings.AnonymousMaxUploadMB) ||
		!validUnlimitedMegabyteLimit(cfg.DefaultSettings.AnonymousDailyUploadMB) ||
		!validUnlimitedMegabyteLimit(cfg.DefaultSettings.UserDailyUploadMB) ||
		cfg.DefaultSettings.DefaultUserStorageMB <= 0 ||
		cfg.DefaultSettings.UsageRetentionDays <= 0 ||
		cfg.DefaultSettings.LocalStorageMaxGB <= 0 ||
		cfg.DefaultSettings.AnonymousMaxDays <= 0 ||
		cfg.DefaultSettings.UserMaxDays <= 0 ||
		cfg.DefaultSettings.AnonymousDailyBoxes <= 0 ||
		cfg.DefaultSettings.UserDailyBoxes <= 0 ||
		cfg.DefaultSettings.AnonymousActiveBoxes <= 0 ||
		cfg.DefaultSettings.UserActiveBoxes <= 0 ||
		cfg.DefaultSettings.ShortWindowRequests <= 0 ||
		cfg.DefaultSettings.ShortWindowSeconds <= 0 {
		return Config{}, fmt.Errorf("upload policy settings must be positive, with -1 allowed for upload MB limits")
	}

	return cfg, nil
}

func defaultPath(name string) string {
	candidates := []string{
		filepath.Join("backend", name),
		name,
	}

	for _, candidate := range candidates {
		if info, err := os.Stat(candidate); err == nil && info.IsDir() {
			return candidate
		}
	}

	return filepath.Join("backend", name)
}

func envString(key, fallback string) string {
	if value := strings.TrimSpace(os.Getenv(key)); value != "" {
		return value
	}
	return fallback
}

func envDuration(key string, fallback time.Duration) time.Duration {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return fallback
	}

	parsed, err := time.ParseDuration(value)
	if err != nil {
		return fallback
	}
	return parsed
}

func envBool(key string, fallback bool) bool {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return fallback
	}

	parsed, err := strconv.ParseBool(value)
	if err != nil {
		return fallback
	}
	return parsed
}

func envInt(key string, fallback int) int {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return fallback
	}

	parsed, err := strconv.Atoi(value)
	if err != nil {
		return fallback
	}
	return parsed
}

func envMegabytes(key string, fallback float64) int64 {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return megabytesToBytes(fallback)
	}

	parsed, err := parseMegabytes(value)
	if err != nil {
		return megabytesToBytes(fallback)
	}
	return parsed
}

func envMegabytesFloat(key string, fallback float64) float64 {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return fallback
	}
	parsed, err := parseMegabytesFloat(value)
	if err != nil {
		return fallback
	}
	return parsed
}

func envMegabytesLimitFloat(key string, fallback float64) float64 {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return fallback
	}
	parsed, err := parseMegabytesLimitFloat(value)
	if err != nil {
		return fallback
	}
	return parsed
}

func envGigabytesFloat(key string, fallback float64) float64 {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return fallback
	}
	normalized := strings.TrimSpace(value)
	normalized = strings.TrimSuffix(normalized, "GB")
	normalized = strings.TrimSuffix(normalized, "Gb")
	normalized = strings.TrimSuffix(normalized, "gb")
	normalized = strings.TrimSpace(normalized)
	parsed, err := strconv.ParseFloat(normalized, 64)
	if err != nil || parsed <= 0 {
		return fallback
	}
	return parsed
}

func parseMegabytes(value string) (int64, error) {
	sizeMB, err := parseMegabytesFloat(value)
	if err != nil {
		return 0, err
	}
	return megabytesToBytes(sizeMB), nil
}

func parseMegabytesFloat(value string) (float64, error) {
	normalized := strings.TrimSpace(value)
	normalized = strings.TrimSuffix(normalized, "MB")
	normalized = strings.TrimSuffix(normalized, "Mb")
	normalized = strings.TrimSuffix(normalized, "mb")
	normalized = strings.TrimSpace(normalized)

	sizeMB, err := strconv.ParseFloat(normalized, 64)
	if err != nil {
		return 0, fmt.Errorf("invalid megabyte value %q: %w", value, err)
	}
	if sizeMB <= 0 {
		return 0, fmt.Errorf("megabyte value must be positive")
	}

	return sizeMB, nil
}

func parseMegabytesLimitFloat(value string) (float64, error) {
	sizeMB, err := parseMegabytesFloatAllowNegativeOne(value)
	if err != nil {
		return 0, err
	}
	if !validUnlimitedMegabyteLimit(sizeMB) {
		return 0, fmt.Errorf("megabyte value must be positive or -1 for unlimited")
	}
	return sizeMB, nil
}

func parseMegabytesFloatAllowNegativeOne(value string) (float64, error) {
	normalized := strings.TrimSpace(value)
	normalized = strings.TrimSuffix(normalized, "MB")
	normalized = strings.TrimSuffix(normalized, "Mb")
	normalized = strings.TrimSuffix(normalized, "mb")
	normalized = strings.TrimSpace(normalized)

	sizeMB, err := strconv.ParseFloat(normalized, 64)
	if err != nil {
		return 0, fmt.Errorf("invalid megabyte value %q: %w", value, err)
	}
	return sizeMB, nil
}

func validUnlimitedMegabyteLimit(value float64) bool {
	return value > 0 || value == -1
}

func megabytesToBytes(sizeMB float64) int64 {
	return int64(math.Round(sizeMB * 1024 * 1024))
}
