package config

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

func Load() (*Config, error) {
	cfg := &Config{
		DataDir:                       "./data",
		AdminUsername:                 "admin",
		AdminEnabled:                  AdminEnabledAuto,
		AllowAdminSettingsOverride:    true,
		GuestUploadsEnabled:           true,
		APIEnabled:                    true,
		ZipDownloadsEnabled:           true,
		OneTimeDownloadsEnabled:       true,
		OneTimeDownloadExpirySeconds:  7 * 24 * 60 * 60,
		OneTimeDownloadRetryOnFailure: false,
		DefaultGuestExpirySeconds:     10,
		MaxGuestExpirySeconds:         48 * 60 * 60,
		SessionTTLSeconds:             24 * 60 * 60,
		BoxPollIntervalMS:             5000,
		ThumbnailBatchSize:            10,
		ThumbnailIntervalSeconds:      30,
		ActivityRetentionSeconds:      7 * 24 * 60 * 60,
		SecurityLoginWindowSeconds:    10 * 60,
		SecurityLoginMaxAttempts:      8,
		SecurityBanSeconds:            30 * 60,
		SecurityScanWindowSeconds:     5 * 60,
		SecurityScanMaxAttempts:       12,
		SecurityUploadWindowSeconds:   60,
		SecurityUploadMaxRequests:     20,
		SecurityUploadMaxBytes:        10 * 1024 * 1024 * 1024,
		sources:                       make(map[string]Source),
		values:                        make(map[string]string),
		defaults:                      make(map[string]string),
	}

	// Config precedence: defaults -> env -> overrides.
	// Overrides are applied after Load by the server once the metadata store opens.
	cfg.captureDefaults()

	if err := cfg.applyStringEnv(SettingDataDir, "WARPBOX_DATA_DIR", &cfg.DataDir); err != nil {
		return nil, err
	}
	if err := cfg.applyStringEnv("", "WARPBOX_ADMIN_PASSWORD", &cfg.AdminPassword); err != nil {
		return nil, err
	}
	if err := cfg.applyStringEnv("", "WARPBOX_ADMIN_USERNAME", &cfg.AdminUsername); err != nil {
		return nil, err
	}
	if err := cfg.applyStringEnv("", "WARPBOX_ADMIN_EMAIL", &cfg.AdminEmail); err != nil {
		return nil, err
	}
	if err := cfg.applyStringEnv(SettingSecurityIPWhitelist, "WARPBOX_SECURITY_IP_WHITELIST", &cfg.SecurityIPWhitelist); err != nil {
		return nil, err
	}
	if err := cfg.applyStringEnv(SettingSecurityAdminIPWhitelist, "WARPBOX_SECURITY_ADMIN_IP_WHITELIST", &cfg.SecurityAdminIPWhitelist); err != nil {
		return nil, err
	}
	if err := cfg.applyStringEnv(SettingTrustedProxyCIDRs, "WARPBOX_TRUSTED_PROXY_CIDRS", &cfg.TrustedProxyCIDRs); err != nil {
		return nil, err
	}
	if raw := strings.TrimSpace(os.Getenv("WARPBOX_ADMIN_ENABLED")); raw != "" {
		mode := AdminEnabledMode(strings.ToLower(raw))
		if mode != AdminEnabledAuto && mode != AdminEnabledTrue && mode != AdminEnabledFalse {
			return nil, fmt.Errorf("WARPBOX_ADMIN_ENABLED must be auto, true, or false")
		}
		cfg.AdminEnabled = mode
	}
	if err := cfg.applyBoolEnv("", "WARPBOX_ALLOW_ADMIN_SETTINGS_OVERRIDE", &cfg.AllowAdminSettingsOverride); err != nil {
		return nil, err
	}
	if err := cfg.applyBoolEnv("", "WARPBOX_ADMIN_COOKIE_SECURE", &cfg.AdminCookieSecure); err != nil {
		return nil, err
	}

	envBools := []struct {
		key    string
		name   string
		target *bool
	}{
		{SettingGuestUploadsEnabled, "WARPBOX_GUEST_UPLOADS_ENABLED", &cfg.GuestUploadsEnabled},
		{SettingAPIEnabled, "WARPBOX_API_ENABLED", &cfg.APIEnabled},
		{SettingZipDownloadsEnabled, "WARPBOX_ZIP_DOWNLOADS_ENABLED", &cfg.ZipDownloadsEnabled},
		{SettingOneTimeDownloadsEnabled, "WARPBOX_ONE_TIME_DOWNLOADS_ENABLED", &cfg.OneTimeDownloadsEnabled},
		{SettingOneTimeDownloadRetryFail, "WARPBOX_ONE_TIME_DOWNLOAD_RETRY_ON_FAILURE", &cfg.OneTimeDownloadRetryOnFailure},
		{SettingRenewOnAccessEnabled, "WARPBOX_RENEW_ON_ACCESS_ENABLED", &cfg.RenewOnAccessEnabled},
		{SettingRenewOnDownloadEnabled, "WARPBOX_RENEW_ON_DOWNLOAD_ENABLED", &cfg.RenewOnDownloadEnabled},
	}
	for _, item := range envBools {
		if err := cfg.applyBoolEnv(item.key, item.name, item.target); err != nil {
			return nil, err
		}
	}

	envInt64s := []struct {
		key    string
		name   string
		min    int64
		target *int64
	}{
		{SettingDefaultGuestExpirySecs, "WARPBOX_DEFAULT_GUEST_EXPIRY_SECONDS", 0, &cfg.DefaultGuestExpirySeconds},
		{SettingMaxGuestExpirySecs, "WARPBOX_MAX_GUEST_EXPIRY_SECONDS", 0, &cfg.MaxGuestExpirySeconds},
		{SettingOneTimeDownloadExpirySecs, "WARPBOX_ONE_TIME_DOWNLOAD_EXPIRY_SECONDS", 0, &cfg.OneTimeDownloadExpirySeconds},
		{SettingSessionTTLSeconds, "WARPBOX_SESSION_TTL_SECONDS", 60, &cfg.SessionTTLSeconds},
		{SettingActivityRetentionSeconds, "WARPBOX_ACTIVITY_RETENTION_SECONDS", 60, &cfg.ActivityRetentionSeconds},
		{SettingSecurityLoginWindowSecs, "WARPBOX_SECURITY_LOGIN_WINDOW_SECONDS", 10, &cfg.SecurityLoginWindowSeconds},
		{SettingSecurityBanSeconds, "WARPBOX_SECURITY_BAN_SECONDS", 10, &cfg.SecurityBanSeconds},
		{SettingSecurityScanWindowSecs, "WARPBOX_SECURITY_SCAN_WINDOW_SECONDS", 10, &cfg.SecurityScanWindowSeconds},
		{SettingSecurityUploadWindowSecs, "WARPBOX_SECURITY_UPLOAD_WINDOW_SECONDS", 10, &cfg.SecurityUploadWindowSeconds},
	}
	for _, item := range envInt64s {
		if err := cfg.applyInt64Env(item.key, item.name, item.min, item.target); err != nil {
			return nil, err
		}
	}
	sizeEnvVars := []struct {
		key       string
		gbName    string
		mbName    string
		bytesName string
		target    *int64
	}{
		{SettingGlobalMaxFileSizeBytes, "WARPBOX_GLOBAL_MAX_FILE_SIZE_GB", "WARPBOX_GLOBAL_MAX_FILE_SIZE_MB", "WARPBOX_GLOBAL_MAX_FILE_SIZE_BYTES", &cfg.GlobalMaxFileSizeBytes},
		{SettingGlobalMaxBoxSizeBytes, "WARPBOX_GLOBAL_MAX_BOX_SIZE_GB", "WARPBOX_GLOBAL_MAX_BOX_SIZE_MB", "WARPBOX_GLOBAL_MAX_BOX_SIZE_BYTES", &cfg.GlobalMaxBoxSizeBytes},
		{SettingDefaultUserMaxFileBytes, "WARPBOX_DEFAULT_USER_MAX_FILE_SIZE_GB", "WARPBOX_DEFAULT_USER_MAX_FILE_SIZE_MB", "WARPBOX_DEFAULT_USER_MAX_FILE_SIZE_BYTES", &cfg.DefaultUserMaxFileSizeBytes},
		{SettingDefaultUserMaxBoxBytes, "WARPBOX_DEFAULT_USER_MAX_BOX_SIZE_GB", "WARPBOX_DEFAULT_USER_MAX_BOX_SIZE_MB", "WARPBOX_DEFAULT_USER_MAX_BOX_SIZE_BYTES", &cfg.DefaultUserMaxBoxSizeBytes},
		{SettingSecurityUploadMaxGB, "WARPBOX_SECURITY_UPLOAD_MAX_GB", "WARPBOX_SECURITY_UPLOAD_MAX_MB", "WARPBOX_SECURITY_UPLOAD_MAX_BYTES", &cfg.SecurityUploadMaxBytes},
	}
	for _, item := range sizeEnvVars {
		if err := cfg.applySizeEnv(item.key, item.gbName, item.mbName, item.bytesName, 0, item.target); err != nil {
			return nil, err
		}
	}

	envInts := []struct {
		key    string
		name   string
		min    int
		target *int
	}{
		{SettingBoxPollIntervalMS, "WARPBOX_BOX_POLL_INTERVAL_MS", 1000, &cfg.BoxPollIntervalMS},
		{SettingThumbnailBatchSize, "WARPBOX_THUMBNAIL_BATCH_SIZE", 1, &cfg.ThumbnailBatchSize},
		{SettingThumbnailIntervalSeconds, "WARPBOX_THUMBNAIL_INTERVAL_SECONDS", 1, &cfg.ThumbnailIntervalSeconds},
		{SettingSecurityLoginMaxAttempts, "WARPBOX_SECURITY_LOGIN_MAX_ATTEMPTS", 1, &cfg.SecurityLoginMaxAttempts},
		{SettingSecurityScanMaxAttempts, "WARPBOX_SECURITY_SCAN_MAX_ATTEMPTS", 1, &cfg.SecurityScanMaxAttempts},
		{SettingSecurityUploadMaxRequests, "WARPBOX_SECURITY_UPLOAD_MAX_REQUESTS", 1, &cfg.SecurityUploadMaxRequests},
	}
	for _, item := range envInts {
		if err := cfg.applyIntEnv(item.key, item.name, item.min, item.target); err != nil {
			return nil, err
		}
	}

	cfg.DataDir = filepath.Clean(cfg.DataDir)
	if strings.TrimSpace(cfg.DataDir) == "" || cfg.DataDir == "." && strings.TrimSpace(os.Getenv("WARPBOX_DATA_DIR")) == "" {
		cfg.DataDir = "data"
	}
	if cfg.AdminUsername = strings.TrimSpace(cfg.AdminUsername); cfg.AdminUsername == "" {
		return nil, fmt.Errorf("WARPBOX_ADMIN_USERNAME cannot be empty")
	}
	cfg.AdminEmail = strings.TrimSpace(cfg.AdminEmail)
	if err := validateSecurityTextSetting(SettingSecurityIPWhitelist, cfg.SecurityIPWhitelist); err != nil {
		return nil, err
	}
	if err := validateSecurityTextSetting(SettingSecurityAdminIPWhitelist, cfg.SecurityAdminIPWhitelist); err != nil {
		return nil, err
	}
	if err := validateSecurityTextSetting(SettingTrustedProxyCIDRs, cfg.TrustedProxyCIDRs); err != nil {
		return nil, err
	}
	cfg.UploadsDir = filepath.Join(cfg.DataDir, "uploads")
	cfg.DBDir = filepath.Join(cfg.DataDir, "db")
	cfg.setValue(SettingDataDir, cfg.DataDir, cfg.sourceFor(SettingDataDir))
	return cfg, nil
}

func (cfg *Config) EnsureDirectories() error {
	for _, path := range []string{cfg.DataDir, cfg.UploadsDir, cfg.DBDir} {
		if err := os.MkdirAll(path, 0755); err != nil {
			return fmt.Errorf("create %s: %w", path, err)
		}
	}
	return nil
}
func (cfg *Config) captureDefaults() {
	cfg.captureDefaultValue(SettingDataDir, cfg.DataDir)
	cfg.captureDefaultValue(SettingGuestUploadsEnabled, formatBool(cfg.GuestUploadsEnabled))
	cfg.captureDefaultValue(SettingAPIEnabled, formatBool(cfg.APIEnabled))
	cfg.captureDefaultValue(SettingZipDownloadsEnabled, formatBool(cfg.ZipDownloadsEnabled))
	cfg.captureDefaultValue(SettingOneTimeDownloadsEnabled, formatBool(cfg.OneTimeDownloadsEnabled))
	cfg.captureDefaultValue(SettingOneTimeDownloadExpirySecs, strconv.FormatInt(cfg.OneTimeDownloadExpirySeconds, 10))
	cfg.captureDefaultValue(SettingOneTimeDownloadRetryFail, formatBool(cfg.OneTimeDownloadRetryOnFailure))
	cfg.captureDefaultValue(SettingRenewOnAccessEnabled, formatBool(cfg.RenewOnAccessEnabled))
	cfg.captureDefaultValue(SettingRenewOnDownloadEnabled, formatBool(cfg.RenewOnDownloadEnabled))
	cfg.captureDefaultValue(SettingDefaultGuestExpirySecs, strconv.FormatInt(cfg.DefaultGuestExpirySeconds, 10))
	cfg.captureDefaultValue(SettingMaxGuestExpirySecs, strconv.FormatInt(cfg.MaxGuestExpirySeconds, 10))
	cfg.captureDefaultValue(SettingGlobalMaxFileSizeBytes, formatGigabytesFromBytes(cfg.GlobalMaxFileSizeBytes))
	cfg.captureDefaultValue(SettingGlobalMaxBoxSizeBytes, formatGigabytesFromBytes(cfg.GlobalMaxBoxSizeBytes))
	cfg.captureDefaultValue(SettingDefaultUserMaxFileBytes, formatGigabytesFromBytes(cfg.DefaultUserMaxFileSizeBytes))
	cfg.captureDefaultValue(SettingDefaultUserMaxBoxBytes, formatGigabytesFromBytes(cfg.DefaultUserMaxBoxSizeBytes))
	cfg.captureDefaultValue(SettingSessionTTLSeconds, strconv.FormatInt(cfg.SessionTTLSeconds, 10))
	cfg.captureDefaultValue(SettingBoxPollIntervalMS, strconv.Itoa(cfg.BoxPollIntervalMS))
	cfg.captureDefaultValue(SettingThumbnailBatchSize, strconv.Itoa(cfg.ThumbnailBatchSize))
	cfg.captureDefaultValue(SettingThumbnailIntervalSeconds, strconv.Itoa(cfg.ThumbnailIntervalSeconds))
	cfg.captureDefaultValue(SettingActivityRetentionSeconds, strconv.FormatInt(cfg.ActivityRetentionSeconds, 10))
	cfg.captureDefaultValue(SettingSecurityIPWhitelist, cfg.SecurityIPWhitelist)
	cfg.captureDefaultValue(SettingSecurityAdminIPWhitelist, cfg.SecurityAdminIPWhitelist)
	cfg.captureDefaultValue(SettingTrustedProxyCIDRs, cfg.TrustedProxyCIDRs)
	cfg.captureDefaultValue(SettingSecurityLoginWindowSecs, strconv.FormatInt(cfg.SecurityLoginWindowSeconds, 10))
	cfg.captureDefaultValue(SettingSecurityLoginMaxAttempts, strconv.Itoa(cfg.SecurityLoginMaxAttempts))
	cfg.captureDefaultValue(SettingSecurityBanSeconds, strconv.FormatInt(cfg.SecurityBanSeconds, 10))
	cfg.captureDefaultValue(SettingSecurityScanWindowSecs, strconv.FormatInt(cfg.SecurityScanWindowSeconds, 10))
	cfg.captureDefaultValue(SettingSecurityScanMaxAttempts, strconv.Itoa(cfg.SecurityScanMaxAttempts))
	cfg.captureDefaultValue(SettingSecurityUploadWindowSecs, strconv.FormatInt(cfg.SecurityUploadWindowSeconds, 10))
	cfg.captureDefaultValue(SettingSecurityUploadMaxRequests, strconv.Itoa(cfg.SecurityUploadMaxRequests))
	cfg.captureDefaultValue(SettingSecurityUploadMaxGB, formatGigabytesFromBytes(cfg.SecurityUploadMaxBytes))
}

func (cfg *Config) captureDefaultValue(key string, value string) {
	cfg.setValue(key, value, SourceDefault)
	if cfg.defaults != nil {
		cfg.defaults[key] = value
	}
}

func (cfg *Config) applyStringEnv(key string, name string, target *string) error {
	raw := os.Getenv(name)
	if raw == "" {
		return nil
	}
	*target = raw
	if key != "" {
		cfg.setValue(key, raw, SourceEnv)
	}
	return nil
}

func (cfg *Config) applyBoolEnv(key string, name string, target *bool) error {
	raw := strings.TrimSpace(os.Getenv(name))
	if raw == "" {
		return nil
	}
	parsed, err := parseBool(raw)
	if err != nil {
		return fmt.Errorf("%s: %w", name, err)
	}
	*target = parsed
	if key != "" {
		cfg.setValue(key, formatBool(parsed), SourceEnv)
	}
	return nil
}

func (cfg *Config) applyInt64Env(key string, name string, min int64, target *int64) error {
	raw := strings.TrimSpace(os.Getenv(name))
	if raw == "" {
		return nil
	}
	parsed, err := parseInt64(raw, min)
	if err != nil {
		return fmt.Errorf("%s: %w", name, err)
	}
	*target = parsed
	if key != "" {
		cfg.setValue(key, strconv.FormatInt(parsed, 10), SourceEnv)
	}
	return nil
}

func (cfg *Config) applySizeEnv(key string, gbName string, mbName string, bytesName string, min int64, target *int64) error {
	if rawGB := strings.TrimSpace(os.Getenv(gbName)); rawGB != "" {
		parsed, err := parseGigabytes(rawGB, float64(min))
		if err != nil {
			return fmt.Errorf("%s: %w", gbName, err)
		}
		*target = parsed
		cfg.setValue(key, formatGigabytesFromBytes(parsed), SourceEnv)
		return nil
	}
	if rawBytes := strings.TrimSpace(os.Getenv(bytesName)); rawBytes != "" {
		parsed, err := parseInt64(rawBytes, min)
		if err != nil {
			return fmt.Errorf("%s: %w", bytesName, err)
		}
		*target = parsed
		cfg.setValue(key, formatGigabytesFromBytes(parsed), SourceEnv)
		return nil
	}

	rawMB := strings.TrimSpace(os.Getenv(mbName))
	if rawMB == "" {
		return nil
	}
	parsedMB, err := parseInt64(rawMB, min)
	if err != nil {
		return fmt.Errorf("%s: %w", mbName, err)
	}
	parsedBytes := parsedMB * 1000 * 1000
	*target = parsedBytes
	cfg.setValue(key, formatGigabytesFromBytes(parsedBytes), SourceEnv)
	return nil
}

func (cfg *Config) applyIntEnv(key string, name string, min int, target *int) error {
	raw := strings.TrimSpace(os.Getenv(name))
	if raw == "" {
		return nil
	}
	parsed, err := parseInt(raw, min)
	if err != nil {
		return fmt.Errorf("%s: %w", name, err)
	}
	*target = parsed
	if key != "" {
		cfg.setValue(key, strconv.Itoa(parsed), SourceEnv)
	}
	return nil
}
