package config

import (
	"fmt"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"
)

type Config struct {
	AppPort string
	AppEnv  string

	SAMLCertFile     string
	SAMLKeyFile      string
	SAMLMetadataFile string
	SAMLRootURL      string

	RedisAddr     string
	RedisPassword string
	RedisDB       int

	FrontendURL string

	CookieDomain   string
	CookieSecure   bool
	SessionTTLHour int
}

type SAMLConfig struct {
	CertFile     string
	KeyFile      string
	MetadataFile string
	RootURL      string
	FrontendURL  string
	CookieDomain string
	CookieSecure bool
	SessionTTLHr int
}

func LoadEnvConfig() Config {
	return Config{
		AppPort:          envString("APP_PORT", "3000"),
		AppEnv:           envString("APP_ENV", "development"),
		SAMLCertFile:     envString("SAML_CERT_FILE", "configs/saml.crt"),
		SAMLKeyFile:      envString("SAML_KEY_FILE", "configs/saml.key"),
		SAMLMetadataFile: envString("SAML_METADATA_FILE", "configs/metadata.xml"),
		SAMLRootURL:      envString("SAML_ROOT_URL", "https://api-wfm-dev.mybit.co.id"),
		RedisAddr:        envString("REDIS_ADDR", "localhost:6379"),
		RedisPassword:    envString("REDIS_PASSWORD", ""),
		RedisDB:          envInt("REDIS_DB", 0),
		FrontendURL:      envString("FRONTEND_URL", "https://wfm-dev.mybit.co.id"),
		CookieDomain:     strings.TrimSpace(os.Getenv("COOKIE_DOMAIN")),
		CookieSecure:     envBool("COOKIE_SECURE", true),
		SessionTTLHour:   envInt("SESSION_TTL_HOUR", 8),
	}
}

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

func envInt(key string, fallback int) int {
	v := strings.TrimSpace(os.Getenv(key))
	if v == "" {
		return fallback
	}
	n, err := strconv.Atoi(v)
	if err != nil {
		return fallback
	}
	return n
}

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

type MySQLConfig struct {
	DSN             string
	MaxOpenConns    int
	MaxIdleConns    int
	ConnMaxLifetime time.Duration
	ConnMaxIdleTime time.Duration
}

type AuthConfig struct {
	PublicBaseURL                string
	PasswordPepper               string
	IPEncryptionKey              string
	HashTime                     uint32
	HashMemoryKB                 uint32
	HashThreads                  uint8
	HashKeyLen                   uint32
	EmailVerifyTTL               time.Duration
	EmailVerifyURL               string
	InviteTTL                    time.Duration
	SetPasswordURL               string
	PasswordResetTTL             time.Duration
	PasswordResetURL             string
	SecurityPINResetTTL          time.Duration
	SecurityPINResetURL          string
	ForgotSecurityPINCooldown    time.Duration
	ForgotSecurityPINMinDuration time.Duration
	SecurityPINMaxAttempts       int
	SecurityPINLockDuration      time.Duration
	SecurityPINActionTokenTTL    time.Duration
	ForgotPasswordEmailCooldown  time.Duration
	ForgotPasswordMinDuration    time.Duration
	ForgotPasswordIPRateMax      int
	ForgotPasswordIPRateWindow   time.Duration
	LoginIPRateMax               int
	LoginIPRateWindow            time.Duration
	TOTPChallengeTTL             time.Duration
	EmailOTPChallengeTTL         time.Duration
	EmailOTPLength               int
	EmailOTPExpiry               time.Duration
	EmailOTPMaxAttempts          int
	EmailOTPResendCooldown       time.Duration
	EmailOTPMaxResendPerHour     int
	SSOStateTTL                  time.Duration
	TOTPIssuer                   string
	TOTPSecretKeyB64             string
	JWTAccessSecret              string
	JWTRefreshSecret             string
	JWTAccessTTL                 time.Duration
	JWTRefreshTTL                time.Duration
	JWTRefreshTOTPTTL            time.Duration
	JWTLocalRefreshTTL           time.Duration
	JWTSSORefreshTTL             time.Duration
	SessionIdleTTL               time.Duration
	JWTCookieDomain              string
	JWTCookieSecure              bool
	JWTCookieSameSite            string
	JWTAccessCookieName          string
	JWTRefreshCookieName         string
	WebAuthnRPID                 string
	WebAuthnRPDisplayName        string
	WebAuthnRPOrigins            []string
	WebAuthnChallengeTTL         time.Duration
	SSOSuccessRedirectURL        string
	DefaultRoleName              string
	DisableRegister              bool
}

type SESConfig struct {
	Region                string
	Endpoint              string
	From                  string
	ConfigurationSetName  string
	SendTimeout           time.Duration
	AllowInsecureEndpoint bool
}

type EmailDeliveryConfig struct {
	Provider string
	MockDir  string
}

type MicrosoftSSOConfig struct {
	TenantID     string
	ClientID     string
	ClientSecret string
	RedirectURL  string
	Authority    string
	Scopes       []string
}

type HTTPConfig struct {
	BodyLimit            int
	ReadTimeout          time.Duration
	WriteTimeout         time.Duration
	IdleTimeout          time.Duration
	RequestTimeout       time.Duration
	RequestTimeoutByPath map[string]time.Duration
	ShutdownTimeout      time.Duration
	CORSAllowOrigins     string
	CORSAllowMethods     string
	CORSAllowHeaders     string
	CORSExposeHeaders    string
	CORSAllowCredentials bool
	CORSMaxAge           int
	RateLimitMax         int
	RateLimitWindow      time.Duration
	RateLimitByEndpoint  map[string]RateLimitRule
	HSTSMaxAge           int
}

type RateLimitRule struct {
	Max    int
	Window time.Duration
}

type AuditConfig struct {
	QueueSize int
	Workers   int
}

type LoggingConfig struct {
	Level  string
	Format string
}

type CircuitBreakerPolicy struct {
	Enabled             bool
	MaxRequests         uint32
	Interval            time.Duration
	BucketPeriod        time.Duration
	Timeout             time.Duration
	MinRequests         uint32
	FailureRatio        float64
	ConsecutiveFailures uint32
}

type ResilienceConfig struct {
	Default      CircuitBreakerPolicy
	MicrosoftSSO CircuitBreakerPolicy
	SES          CircuitBreakerPolicy
	SQS          CircuitBreakerPolicy
}

type QueueConfig struct {
	MessageSchemaVersion        string
	EnableLegacyPayloadFallback bool
	IdempotencyKeyPrefix        string
	IdempotencyTTL              time.Duration
	ProcessingTTL               time.Duration
	WorkerConcurrency           int
	WorkerMaxBatchSize          int
	WorkerProcessTimeout        time.Duration
	WorkerShutdownTimeout       time.Duration
	WorkerBackoffMin            time.Duration
	WorkerBackoffMax            time.Duration
	WorkerPermanentFailureDelay time.Duration
	WorkerMonitorAddr           string
	WorkerHealthErrorThreshold  int
	WorkerDepthPollInterval     time.Duration
	OutboxEnabled               bool
	OutboxPollInterval          time.Duration
	OutboxBatchSize             int
	OutboxDispatchTimeout       time.Duration
	OutboxLockTTL               time.Duration
	OutboxMaxAttempts           int
}

type FileQueueConfig struct {
	MessageSchemaVersion        string
	IdempotencyKeyPrefix        string
	IdempotencyTTL              time.Duration
	ProcessingTTL               time.Duration
	WorkerConcurrency           int
	WorkerMaxBatchSize          int
	WorkerProcessTimeout        time.Duration
	WorkerShutdownTimeout       time.Duration
	WorkerBackoffMin            time.Duration
	WorkerBackoffMax            time.Duration
	WorkerPermanentFailureDelay time.Duration
	WorkerMonitorAddr           string
	WorkerHealthErrorThreshold  int
	WorkerDepthPollInterval     time.Duration
	OutboxEnabled               bool
	OutboxPollInterval          time.Duration
	OutboxBatchSize             int
	OutboxDispatchTimeout       time.Duration
	OutboxLockTTL               time.Duration
	OutboxMaxAttempts           int
}

type SQSConfig struct {
	Region                string
	AccessKeyID           string
	SecretAccessKey       string
	Endpoint              string
	QueueURL              string
	QueueType             string
	MessageGroupID        string
	MaxMessages           int
	WaitTimeSeconds       int32
	VisibilityTimeout     time.Duration
	AllowInsecureEndpoint bool
}

type FileStorageConfig struct {
	Provider              string
	Bucket                string
	Region                string
	Endpoint              string
	AccessKeyID           string
	SecretAccessKey       string
	ForcePathStyle        bool
	AllowInsecureEndpoint bool
	AutoCreateBucket      bool
	ObjectKeyPrefix       string
	PresignPutTTL         time.Duration
	PresignGetTTL         time.Duration
}

type FileManagerUploadPolicyConfig struct {
	MaxFileSizeBytes  int64
	AllowedMimeTypes  []string
	AllowedExtensions []string
	AllowEmptyFile    bool
	MaxFilenameLength int
}

type FileManagerTrashPolicyConfig struct {
	AutoPurgeEnabled   bool
	Retention          time.Duration
	AutoPurgeInterval  time.Duration
	AutoPurgeBatchSize int
}

type FileManagerLifecycleConfig struct {
	EnableS3Tagging      bool
	EnableCleanupJob     bool
	TempUploadGraceHours int
	OrphanGraceDays      int
	CleanupBatchSize     int
	CleanupDryRun        bool
}

type WOPIConfig struct {
	TokenSecret   string
	ProofSecret   string
	DiscoveryURL  string
	PublicBaseURL string
	EditActionURL string
	TokenTTL      time.Duration
	MaxBodyBytes  int
	RequireProof  bool
}

type GotenbergConfig struct {
	URL string
}

func LoadMySQLConfigFromEnv() MySQLConfig {
	return MySQLConfig{
		DSN:             getenv("MYSQL_DSN", ""),
		MaxOpenConns:    getenvInt("MYSQL_MAX_OPEN_CONNS", 25),
		MaxIdleConns:    getenvInt("MYSQL_MAX_IDLE_CONNS", 10),
		ConnMaxLifetime: getenvDuration("MYSQL_CONN_MAX_LIFETIME", 5*time.Minute),
		ConnMaxIdleTime: getenvDuration("MYSQL_CONN_MAX_IDLE_TIME", 2*time.Minute),
	}
}

func LoadAuthConfigFromEnv() AuthConfig {
	refreshTTL := getenvDuration("AUTH_JWT_REFRESH_TTL", time.Hour)
	refreshTOTPTTL := getenvDuration("AUTH_JWT_REFRESH_TOTP_TTL", refreshTTL)
	passwordPepper := getenv("AUTH_PASSWORD_PEPPER", "")
	ipEncryptionKey := getenv("AUTH_IP_ENCRYPTION_KEY", getenv("AUTH_IP_HMAC_KEY", passwordPepper))

	return AuthConfig{
		PublicBaseURL:                getenv("APP_PUBLIC_BASE_URL", ""),
		PasswordPepper:               passwordPepper,
		IPEncryptionKey:              ipEncryptionKey,
		HashTime:                     uint32(getenvInt("AUTH_HASH_TIME", 1)),
		HashMemoryKB:                 uint32(getenvInt("AUTH_HASH_MEMORY_KB", 64*1024)),
		HashThreads:                  uint8(getenvInt("AUTH_HASH_THREADS", 4)),
		HashKeyLen:                   uint32(getenvInt("AUTH_HASH_KEY_LEN", 32)),
		EmailVerifyTTL:               getenvDuration("AUTH_EMAIL_VERIFY_TTL", 24*time.Hour),
		EmailVerifyURL:               getenv("AUTH_EMAIL_VERIFY_URL", ""),
		InviteTTL:                    getenvDuration("AUTH_INVITE_TTL", 24*time.Hour),
		SetPasswordURL:               getenv("AUTH_SET_PASSWORD_URL", getenv("AUTH_EMAIL_VERIFY_URL", "")),
		PasswordResetTTL:             getenvDuration("AUTH_PASSWORD_RESET_TTL", 20*time.Minute),
		PasswordResetURL:             getenv("AUTH_PASSWORD_RESET_URL", getenv("AUTH_SET_PASSWORD_URL", "")),
		SecurityPINResetTTL:          getenvDuration("AUTH_SECURITY_PIN_RESET_TTL", 20*time.Minute),
		SecurityPINResetURL:          getenv("AUTH_SECURITY_PIN_RESET_URL", getenv("AUTH_PASSWORD_RESET_URL", getenv("AUTH_SET_PASSWORD_URL", ""))),
		ForgotSecurityPINCooldown:    getenvDuration("AUTH_FORGOT_SECURITY_PIN_EMAIL_COOLDOWN", 2*time.Minute),
		ForgotSecurityPINMinDuration: getenvDuration("AUTH_FORGOT_SECURITY_PIN_MIN_DURATION", 150*time.Millisecond),
		SecurityPINMaxAttempts:       getenvInt("AUTH_SECURITY_PIN_MAX_ATTEMPTS", 5),
		SecurityPINLockDuration:      getenvDuration("AUTH_SECURITY_PIN_LOCK_DURATION", 0),
		SecurityPINActionTokenTTL:    getenvDuration("AUTH_SECURITY_PIN_ACTION_TOKEN_TTL", 5*time.Minute),
		ForgotPasswordEmailCooldown:  getenvDuration("AUTH_FORGOT_PASSWORD_EMAIL_COOLDOWN", 2*time.Minute),
		ForgotPasswordMinDuration:    getenvDuration("AUTH_FORGOT_PASSWORD_MIN_DURATION", 150*time.Millisecond),
		ForgotPasswordIPRateMax:      getenvInt("AUTH_FORGOT_PASSWORD_IP_RATE_MAX", 10),
		ForgotPasswordIPRateWindow:   getenvDuration("AUTH_FORGOT_PASSWORD_IP_RATE_WINDOW", 15*time.Minute),
		LoginIPRateMax:               getenvInt("AUTH_LOGIN_IP_RATE_MAX", 5),
		LoginIPRateWindow:            getenvDuration("AUTH_LOGIN_IP_RATE_WINDOW", time.Minute),
		TOTPChallengeTTL:             getenvDuration("AUTH_TOTP_CHALLENGE_TTL", 5*time.Minute),
		EmailOTPChallengeTTL:         getenvDuration("AUTH_EMAIL_OTP_CHALLENGE_TTL", 10*time.Minute),
		EmailOTPLength:               getenvInt("AUTH_EMAIL_OTP_LENGTH", 6),
		EmailOTPExpiry:               getenvDuration("AUTH_EMAIL_OTP_EXPIRY", 10*time.Minute),
		EmailOTPMaxAttempts:          getenvInt("AUTH_EMAIL_OTP_MAX_ATTEMPTS", 5),
		EmailOTPResendCooldown:       getenvDuration("AUTH_EMAIL_OTP_RESEND_COOLDOWN", 60*time.Second),
		EmailOTPMaxResendPerHour:     getenvInt("AUTH_EMAIL_OTP_MAX_RESEND_PER_HOUR", 5),
		SSOStateTTL:                  getenvDuration("AUTH_SSO_STATE_TTL", 10*time.Minute),
		TOTPIssuer:                   getenv("TOTP_ISSUER", "Wucher"),
		TOTPSecretKeyB64:             getenv("TOTP_SECRET_KEY", ""),
		JWTAccessSecret:              getenv("AUTH_JWT_ACCESS_SECRET", ""),
		JWTRefreshSecret:             getenv("AUTH_JWT_REFRESH_SECRET", ""),
		JWTAccessTTL:                 getenvDuration("AUTH_JWT_ACCESS_TTL", 10*time.Minute),
		JWTRefreshTTL:                refreshTTL,
		JWTRefreshTOTPTTL:            refreshTOTPTTL,
		JWTLocalRefreshTTL:           getenvDuration("AUTH_JWT_LOCAL_REFRESH_TTL", 24*time.Hour),
		// Fallback lifetime for Microsoft/SSO sessions when Microsoft does not
		// report refresh_token_expires_in. Defaults to Microsoft's default
		// refresh-token lifetime (90 days); the actual session follows the
		// value Microsoft returns whenever it is present.
		JWTSSORefreshTTL:      getenvDuration("AUTH_JWT_SSO_REFRESH_TTL", 90*24*time.Hour),
		SessionIdleTTL:        getenvDuration("AUTH_SESSION_IDLE_TTL", 15*time.Minute),
		JWTCookieDomain:       getenv("AUTH_JWT_COOKIE_DOMAIN", ""),
		JWTCookieSecure:       getenvBool("AUTH_JWT_COOKIE_SECURE", false),
		JWTCookieSameSite:     getenv("AUTH_JWT_COOKIE_SAMESITE", "Lax"),
		JWTAccessCookieName:   getenv("AUTH_JWT_ACCESS_COOKIE", "wucher_at"),
		JWTRefreshCookieName:  getenv("AUTH_JWT_REFRESH_COOKIE", "wucher_rt"),
		WebAuthnRPID:          getenv("AUTH_WEBAUTHN_RP_ID", ""),
		WebAuthnRPDisplayName: getenv("AUTH_WEBAUTHN_RP_DISPLAY_NAME", "Wucher"),
		WebAuthnRPOrigins:     getenvCSV("AUTH_WEBAUTHN_RP_ORIGINS", ""),
		WebAuthnChallengeTTL:  getenvDuration("AUTH_WEBAUTHN_CHALLENGE_TTL", 5*time.Minute),
		SSOSuccessRedirectURL: getenv("AUTH_SSO_SUCCESS_REDIRECT", ""),
		DefaultRoleName:       getenv("AUTH_DEFAULT_ROLE", "user"),
		DisableRegister:       getenvBool("AUTH_DISABLE_REGISTER", false),
	}
}

func LoadSESConfigFromEnv() SESConfig {
	return SESConfig{
		Region:                getenv("SES_REGION", getenv("AWS_REGION", "")),
		Endpoint:              getenv("SES_ENDPOINT", ""),
		From:                  getenv("SES_FROM", ""),
		ConfigurationSetName:  getenv("SES_CONFIGURATION_SET", ""),
		SendTimeout:           getenvDuration("SES_SEND_TIMEOUT", 10*time.Second),
		AllowInsecureEndpoint: getenvBool("SES_ALLOW_INSECURE_ENDPOINT", false),
	}
}

func LoadEmailDeliveryConfigFromEnv() EmailDeliveryConfig {
	return EmailDeliveryConfig{
		Provider: strings.ToLower(strings.TrimSpace(getenv("EMAIL_PROVIDER", "ses"))),
		MockDir:  strings.TrimSpace(getenv("EMAIL_MOCK_DIR", ".local/emails")),
	}
}

func LoadMicrosoftSSOConfigFromEnv() MicrosoftSSOConfig {
	scopes := strings.Split(getenv("MS_ENTRA_SCOPES", "openid,profile,email,offline_access,User.Read"), ",")
	for i := range scopes {
		scopes[i] = strings.TrimSpace(scopes[i])
	}
	return MicrosoftSSOConfig{
		TenantID:     getenv("MS_ENTRA_TENANT_ID", ""),
		ClientID:     getenv("MS_ENTRA_CLIENT_ID", ""),
		ClientSecret: getenv("MS_ENTRA_CLIENT_SECRET", ""),
		RedirectURL:  getenv("MS_ENTRA_REDIRECT_URL", ""),
		Authority:    getenv("MS_ENTRA_AUTHORITY", "https://login.microsoftonline.com/"),
		Scopes:       scopes,
	}
}

func LoadHTTPConfigFromEnv() HTTPConfig {
	return HTTPConfig{
		BodyLimit:            getenvInt("HTTP_BODY_LIMIT", 4*1024*1024),
		ReadTimeout:          getenvDuration("HTTP_READ_TIMEOUT", 10*time.Second),
		WriteTimeout:         getenvDuration("HTTP_WRITE_TIMEOUT", 15*time.Second),
		IdleTimeout:          getenvDuration("HTTP_IDLE_TIMEOUT", 60*time.Second),
		RequestTimeout:       getenvDuration("HTTP_REQUEST_TIMEOUT", 30*time.Second),
		RequestTimeoutByPath: parseHTTPTimeoutOverrides(getenv("HTTP_REQUEST_TIMEOUT_OVERRIDES", "")),
		ShutdownTimeout:      getenvDuration("HTTP_SHUTDOWN_TIMEOUT", 30*time.Second),
		CORSAllowOrigins:     getenv("HTTP_CORS_ALLOW_ORIGINS", "http://localhost:3000,http://127.0.0.1:3000,http://localhost:5173,http://127.0.0.1:5173"),
		CORSAllowMethods:     getenv("HTTP_CORS_ALLOW_METHODS", "GET,POST,PUT,PATCH,DELETE,OPTIONS,HEAD"),
		CORSAllowHeaders:     getenv("HTTP_CORS_ALLOW_HEADERS", "Origin,Content-Type,Accept,Authorization,X-Requested-With,X-Pin-Verification-Token,X-Device-Type"),
		CORSExposeHeaders:    getenv("HTTP_CORS_EXPOSE_HEADERS", "X-Request-ID"),
		CORSAllowCredentials: getenvBool("HTTP_CORS_ALLOW_CREDENTIALS", true),
		CORSMaxAge:           getenvInt("HTTP_CORS_MAX_AGE", 300),
		RateLimitMax:         getenvInt("HTTP_RATE_LIMIT_MAX", 120),
		RateLimitWindow:      getenvDuration("HTTP_RATE_LIMIT_WINDOW", time.Minute),
		RateLimitByEndpoint:  parseHTTPEndpointRateLimitOverrides(getenv("HTTP_RATE_LIMIT_ENDPOINTS", "")),
		HSTSMaxAge:           getenvInt("HTTP_SECURITY_HSTS_MAX_AGE", 0),
	}
}

func parseHTTPTimeoutOverrides(raw string) map[string]time.Duration {
	parsed := make(map[string]time.Duration)
	if strings.TrimSpace(raw) == "" {
		return parsed
	}

	entries := strings.Split(raw, ",")
	for _, entry := range entries {
		entry = strings.TrimSpace(entry)
		if entry == "" {
			continue
		}

		parts := strings.SplitN(entry, "=", 2)
		if len(parts) != 2 {
			continue
		}

		keyParts := strings.Fields(strings.TrimSpace(parts[0]))
		if len(keyParts) < 2 {
			continue
		}
		method := strings.ToUpper(strings.TrimSpace(keyParts[0]))
		path := strings.Join(keyParts[1:], " ")
		path = strings.TrimSpace(path)
		if method == "" || path == "" {
			continue
		}
		key := method + " " + path

		duration, err := time.ParseDuration(strings.TrimSpace(parts[1]))
		if err != nil || duration <= 0 {
			continue
		}
		parsed[key] = duration
	}

	return parsed
}

func parseHTTPEndpointRateLimitOverrides(raw string) map[string]RateLimitRule {
	parsed := make(map[string]RateLimitRule)
	if strings.TrimSpace(raw) == "" {
		return parsed
	}

	entries := strings.Split(raw, ";")
	for _, entry := range entries {
		entry = strings.TrimSpace(entry)
		if entry == "" {
			continue
		}

		parts := strings.SplitN(entry, "=", 2)
		if len(parts) != 2 {
			continue
		}

		keyParts := strings.Fields(strings.TrimSpace(parts[0]))
		if len(keyParts) < 2 {
			continue
		}
		method := strings.ToUpper(strings.TrimSpace(keyParts[0]))
		path := strings.Join(keyParts[1:], " ")
		path = strings.TrimSpace(path)
		if method == "" || path == "" {
			continue
		}
		key := method + " " + path

		ruleParts := strings.SplitN(strings.TrimSpace(parts[1]), ",", 2)
		if len(ruleParts) != 2 {
			continue
		}
		max, err := strconv.Atoi(strings.TrimSpace(ruleParts[0]))
		if err != nil || max <= 0 {
			continue
		}
		window, err := time.ParseDuration(strings.TrimSpace(ruleParts[1]))
		if err != nil || window <= 0 {
			continue
		}

		parsed[key] = RateLimitRule{
			Max:    max,
			Window: window,
		}
	}

	return parsed
}

func LoadAuditConfigFromEnv() AuditConfig {
	return AuditConfig{
		QueueSize: getenvInt("AUDIT_LOG_QUEUE_SIZE", 1024),
		Workers:   getenvInt("AUDIT_LOG_WORKERS", 2),
	}
}

func LoadLoggingConfigFromEnv() LoggingConfig {
	return LoggingConfig{
		Level:  getenv("LOG_LEVEL", "info"),
		Format: getenv("LOG_FORMAT", "json"),
	}
}

func LoadResilienceConfigFromEnv() ResilienceConfig {
	defaults := loadCircuitBreakerPolicyFromEnv("CB_DEFAULT_", CircuitBreakerPolicy{
		Enabled:             true,
		MaxRequests:         1,
		Interval:            time.Minute,
		BucketPeriod:        10 * time.Second,
		Timeout:             30 * time.Second,
		MinRequests:         5,
		FailureRatio:        0.6,
		ConsecutiveFailures: 5,
	})
	return ResilienceConfig{
		Default:      defaults,
		MicrosoftSSO: loadCircuitBreakerPolicyFromEnv("CB_MICROSOFT_SSO_", defaults),
		SES:          loadCircuitBreakerPolicyFromEnv("CB_SES_", defaults),
		SQS:          loadCircuitBreakerPolicyFromEnv("CB_SQS_", defaults),
	}
}

func LoadQueueConfigFromEnv() QueueConfig {
	return QueueConfig{
		MessageSchemaVersion:        getenv("QUEUE_MESSAGE_SCHEMA_VERSION", "v2"),
		EnableLegacyPayloadFallback: getenvBool("QUEUE_ENABLE_LEGACY_PAYLOAD_FALLBACK", true),
		IdempotencyKeyPrefix:        getenv("QUEUE_IDEMPOTENCY_KEY_PREFIX", "queue:idempotency:email:"),
		IdempotencyTTL:              getenvDuration("QUEUE_IDEMPOTENCY_TTL", 24*time.Hour),
		ProcessingTTL:               getenvDuration("QUEUE_PROCESSING_TTL", 15*time.Minute),
		WorkerConcurrency:           getenvInt("QUEUE_WORKER_CONCURRENCY", 4),
		WorkerMaxBatchSize:          getenvInt("QUEUE_WORKER_MAX_BATCH_SIZE", 10),
		WorkerProcessTimeout:        getenvDuration("QUEUE_WORKER_PROCESS_TIMEOUT", 30*time.Second),
		WorkerShutdownTimeout:       getenvDuration("QUEUE_WORKER_SHUTDOWN_TIMEOUT", 30*time.Second),
		WorkerBackoffMin:            getenvDuration("QUEUE_WORKER_BACKOFF_MIN", time.Second),
		WorkerBackoffMax:            getenvDuration("QUEUE_WORKER_BACKOFF_MAX", 5*time.Minute),
		WorkerPermanentFailureDelay: getenvDuration("QUEUE_WORKER_PERMANENT_FAILURE_DELAY", 30*time.Second),
		WorkerMonitorAddr:           getenv("QUEUE_WORKER_MONITOR_ADDR", "127.0.0.1:9090"),
		WorkerHealthErrorThreshold:  getenvInt("QUEUE_WORKER_HEALTH_ERROR_THRESHOLD", 3),
		WorkerDepthPollInterval:     getenvDuration("QUEUE_WORKER_DEPTH_POLL_INTERVAL", 30*time.Second),
		OutboxEnabled:               getenvBool("QUEUE_OUTBOX_ENABLED", true),
		OutboxPollInterval:          getenvDuration("QUEUE_OUTBOX_POLL_INTERVAL", time.Second),
		OutboxBatchSize:             getenvInt("QUEUE_OUTBOX_BATCH_SIZE", 10),
		OutboxDispatchTimeout:       getenvDuration("QUEUE_OUTBOX_DISPATCH_TIMEOUT", 10*time.Second),
		OutboxLockTTL:               getenvDuration("QUEUE_OUTBOX_LOCK_TTL", time.Minute),
		OutboxMaxAttempts:           getenvInt("QUEUE_OUTBOX_MAX_ATTEMPTS", 20),
	}
}

func LoadFileQueueConfigFromEnv() FileQueueConfig {
	return FileQueueConfig{
		MessageSchemaVersion:        getenv("FILE_QUEUE_MESSAGE_SCHEMA_VERSION", "v1"),
		IdempotencyKeyPrefix:        getenv("FILE_QUEUE_IDEMPOTENCY_KEY_PREFIX", "queue:idempotency:file:"),
		IdempotencyTTL:              getenvDuration("FILE_QUEUE_IDEMPOTENCY_TTL", 24*time.Hour),
		ProcessingTTL:               getenvDuration("FILE_QUEUE_PROCESSING_TTL", 15*time.Minute),
		WorkerConcurrency:           getenvInt("FILE_QUEUE_WORKER_CONCURRENCY", 4),
		WorkerMaxBatchSize:          getenvInt("FILE_QUEUE_WORKER_MAX_BATCH_SIZE", 10),
		WorkerProcessTimeout:        getenvDuration("FILE_QUEUE_WORKER_PROCESS_TIMEOUT", 30*time.Second),
		WorkerShutdownTimeout:       getenvDuration("FILE_QUEUE_WORKER_SHUTDOWN_TIMEOUT", 30*time.Second),
		WorkerBackoffMin:            getenvDuration("FILE_QUEUE_WORKER_BACKOFF_MIN", time.Second),
		WorkerBackoffMax:            getenvDuration("FILE_QUEUE_WORKER_BACKOFF_MAX", 5*time.Minute),
		WorkerPermanentFailureDelay: getenvDuration("FILE_QUEUE_WORKER_PERMANENT_FAILURE_DELAY", 30*time.Second),
		WorkerMonitorAddr:           getenv("FILE_QUEUE_WORKER_MONITOR_ADDR", "127.0.0.1:9091"),
		WorkerHealthErrorThreshold:  getenvInt("FILE_QUEUE_WORKER_HEALTH_ERROR_THRESHOLD", 3),
		WorkerDepthPollInterval:     getenvDuration("FILE_QUEUE_WORKER_DEPTH_POLL_INTERVAL", 30*time.Second),
		OutboxEnabled:               getenvBool("FILE_QUEUE_OUTBOX_ENABLED", true),
		OutboxPollInterval:          getenvDuration("FILE_QUEUE_OUTBOX_POLL_INTERVAL", time.Second),
		OutboxBatchSize:             getenvInt("FILE_QUEUE_OUTBOX_BATCH_SIZE", 10),
		OutboxDispatchTimeout:       getenvDuration("FILE_QUEUE_OUTBOX_DISPATCH_TIMEOUT", 10*time.Second),
		OutboxLockTTL:               getenvDuration("FILE_QUEUE_OUTBOX_LOCK_TTL", time.Minute),
		OutboxMaxAttempts:           getenvInt("FILE_QUEUE_OUTBOX_MAX_ATTEMPTS", 20),
	}
}

func LoadSQSConfigFromEnv() SQSConfig {
	return SQSConfig{
		Region:                getenv("AWS_REGION", ""),
		AccessKeyID:           getenv("AWS_ACCESS_KEY_ID", ""),
		SecretAccessKey:       getenv("AWS_SECRET_ACCESS_KEY", ""),
		Endpoint:              getenv("SQS_ENDPOINT", ""),
		QueueURL:              getenv("SQS_QUEUE_URL", ""),
		QueueType:             getenv("SQS_QUEUE_TYPE", "standard"),
		MessageGroupID:        getenv("SQS_MESSAGE_GROUP_ID", "email"),
		MaxMessages:           getenvInt("SQS_MAX_MESSAGES", 10),
		WaitTimeSeconds:       int32(getenvInt("SQS_WAIT_TIME_SECONDS", 20)),
		VisibilityTimeout:     getenvDuration("SQS_VISIBILITY_TIMEOUT", 60*time.Second),
		AllowInsecureEndpoint: getenvBool("SQS_ALLOW_INSECURE_ENDPOINT", false),
	}
}

func LoadFileSQSConfigFromEnv() SQSConfig {
	return SQSConfig{
		Region:                getenv("FILE_SQS_REGION", getenv("AWS_REGION", "")),
		AccessKeyID:           getenv("AWS_ACCESS_KEY_ID", ""),
		SecretAccessKey:       getenv("AWS_SECRET_ACCESS_KEY", ""),
		Endpoint:              getenv("FILE_SQS_ENDPOINT", getenv("SQS_ENDPOINT", "")),
		QueueURL:              getenv("FILE_SQS_QUEUE_URL", ""),
		QueueType:             getenv("FILE_SQS_QUEUE_TYPE", "standard"),
		MessageGroupID:        getenv("FILE_SQS_MESSAGE_GROUP_ID", "file-processing"),
		MaxMessages:           getenvInt("FILE_SQS_MAX_MESSAGES", 10),
		WaitTimeSeconds:       int32(getenvInt("FILE_SQS_WAIT_TIME_SECONDS", 20)),
		VisibilityTimeout:     getenvDuration("FILE_SQS_VISIBILITY_TIMEOUT", 60*time.Second),
		AllowInsecureEndpoint: getenvBool("FILE_SQS_ALLOW_INSECURE_ENDPOINT", getenvBool("SQS_ALLOW_INSECURE_ENDPOINT", false)),
	}
}

func ValidateDistinctQueueURLs(emailCfg, fileCfg SQSConfig) error {
	emailQueueURL := canonicalQueueURL(emailCfg.QueueURL)
	fileQueueURL := canonicalQueueURL(fileCfg.QueueURL)

	if emailQueueURL == "" || fileQueueURL == "" {
		return nil
	}
	if emailQueueURL == fileQueueURL {
		return fmt.Errorf("invalid queue configuration: FILE_SQS_QUEUE_URL must be different from SQS_QUEUE_URL")
	}
	return nil
}

func canonicalQueueURL(raw string) string {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return ""
	}

	parsed, err := url.Parse(trimmed)
	if err != nil {
		return strings.TrimRight(strings.ToLower(trimmed), "/")
	}
	parsed.Host = strings.ToLower(parsed.Host)
	parsed.Path = strings.TrimRight(parsed.Path, "/")
	parsed.RawQuery = ""
	parsed.Fragment = ""
	return parsed.String()
}

func LoadFileStorageConfigFromEnv() FileStorageConfig {
	provider := strings.ToLower(strings.TrimSpace(getenv("FILE_STORAGE_PROVIDER", "s3")))
	return FileStorageConfig{
		Provider:              provider,
		Bucket:                strings.TrimSpace(getenv("FILE_S3_BUCKET", "")),
		Region:                strings.TrimSpace(getenv("FILE_S3_REGION", getenv("AWS_REGION", ""))),
		Endpoint:              strings.TrimSpace(getenv("FILE_S3_ENDPOINT", "")),
		AccessKeyID:           strings.TrimSpace(getenv("FILE_S3_ACCESS_KEY_ID", getenv("AWS_ACCESS_KEY_ID", ""))),
		SecretAccessKey:       strings.TrimSpace(getenv("FILE_S3_SECRET_ACCESS_KEY", getenv("AWS_SECRET_ACCESS_KEY", ""))),
		ForcePathStyle:        getenvBool("FILE_S3_FORCE_PATH_STYLE", false),
		AllowInsecureEndpoint: getenvBool("FILE_S3_ALLOW_INSECURE_ENDPOINT", false),
		AutoCreateBucket:      getenvBool("FILE_S3_AUTO_CREATE_BUCKET", false),
		ObjectKeyPrefix:       strings.TrimSpace(getenv("FILE_OBJECT_KEY_PREFIX", "fm/objects")),
		PresignPutTTL:         getenvDuration("FILE_S3_PRESIGN_PUT_TTL", 15*time.Minute),
		PresignGetTTL:         getenvDuration("FILE_S3_PRESIGN_GET_TTL", 15*time.Minute),
	}
}

func LoadFileManagerUploadPolicyConfigFromEnv() FileManagerUploadPolicyConfig {
	return FileManagerUploadPolicyConfig{
		MaxFileSizeBytes:  int64(getenvInt("FILE_MANAGER_UPLOAD_MAX_FILE_SIZE_BYTES", 4*1024*1024)),
		AllowedMimeTypes:  normalizeMimeTypes(getenvCSV("FILE_MANAGER_UPLOAD_ALLOWED_MIME_TYPES", "")),
		AllowedExtensions: normalizeExtensions(getenvCSV("FILE_MANAGER_UPLOAD_ALLOWED_EXTENSIONS", "")),
		AllowEmptyFile:    getenvBool("FILE_MANAGER_UPLOAD_ALLOW_EMPTY_FILE", false),
		MaxFilenameLength: getenvInt("FILE_MANAGER_UPLOAD_MAX_FILENAME_LENGTH", 255),
	}
}

func LoadFileManagerTrashPolicyConfigFromEnv() FileManagerTrashPolicyConfig {
	retention := getenvDuration("FILE_MANAGER_TRASH_RETENTION", 30*24*time.Hour)
	if retention < 0 {
		retention = 0
	}
	interval := getenvDuration("FILE_MANAGER_TRASH_AUTO_PURGE_INTERVAL", time.Hour)
	if interval <= 0 {
		interval = time.Hour
	}
	batchSize := getenvInt("FILE_MANAGER_TRASH_AUTO_PURGE_BATCH_SIZE", 200)
	if batchSize <= 0 {
		batchSize = 200
	}

	return FileManagerTrashPolicyConfig{
		AutoPurgeEnabled:   getenvBool("FILE_MANAGER_TRASH_AUTO_PURGE_ENABLED", true),
		Retention:          retention,
		AutoPurgeInterval:  interval,
		AutoPurgeBatchSize: batchSize,
	}
}

func LoadFileManagerLifecycleConfigFromEnv() FileManagerLifecycleConfig {
	tempHours := getenvInt("TEMP_UPLOAD_GRACE_HOURS", 2)
	if tempHours <= 0 {
		tempHours = 2
	}
	orphanDays := getenvInt("ORPHAN_GRACE_PERIOD_DAYS", 30)
	if orphanDays <= 0 {
		orphanDays = 30
	}
	batchSize := getenvInt("FILE_CLEANUP_BATCH_SIZE", 100)
	if batchSize <= 0 {
		batchSize = 100
	}
	return FileManagerLifecycleConfig{
		EnableS3Tagging:      getenvBool("ENABLE_S3_TAGGING", true),
		EnableCleanupJob:     getenvBool("ENABLE_FILE_CLEANUP_JOB", false),
		TempUploadGraceHours: tempHours,
		OrphanGraceDays:      orphanDays,
		CleanupBatchSize:     batchSize,
		CleanupDryRun:        getenvBool("FILE_CLEANUP_DRY_RUN", true),
	}
}

func getenv(key, def string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return def
}

func getenvInt(key string, def int) int {
	if v := os.Getenv(key); v != "" {
		if n, err := strconv.Atoi(v); err == nil {
			return n
		}
	}
	return def
}

func getenvUint32(key string, def uint32) uint32 {
	if v := os.Getenv(key); v != "" {
		if n, err := strconv.ParseUint(v, 10, 32); err == nil {
			return uint32(n)
		}
	}
	return def
}

func getenvFloat64(key string, def float64) float64 {
	if v := os.Getenv(key); v != "" {
		if n, err := strconv.ParseFloat(v, 64); err == nil {
			return n
		}
	}
	return def
}

func getenvDuration(key string, def time.Duration) time.Duration {
	if v := os.Getenv(key); v != "" {
		if d, err := time.ParseDuration(v); err == nil {
			return d
		}
	}
	return def
}

func getenvBool(key string, def bool) bool {
	if v := os.Getenv(key); v != "" {
		if b, err := strconv.ParseBool(v); err == nil {
			return b
		}
	}
	return def
}

func getenvCSV(key, def string) []string {
	raw := os.Getenv(key)
	if strings.TrimSpace(raw) == "" {
		raw = def
	}
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return nil
	}

	parts := strings.Split(raw, ",")
	out := make([]string, 0, len(parts))
	for i := range parts {
		part := strings.TrimSpace(parts[i])
		if part == "" {
			continue
		}
		out = append(out, part)
	}
	return out
}

func normalizeMimeTypes(values []string) []string {
	if len(values) == 0 {
		return nil
	}

	out := make([]string, 0, len(values))
	seen := make(map[string]struct{}, len(values))
	for i := range values {
		v := strings.ToLower(strings.TrimSpace(values[i]))
		if idx := strings.Index(v, ";"); idx >= 0 {
			v = strings.TrimSpace(v[:idx])
		}
		if v == "" {
			continue
		}
		if _, ok := seen[v]; ok {
			continue
		}
		seen[v] = struct{}{}
		out = append(out, v)
	}
	return out
}

func normalizeExtensions(values []string) []string {
	if len(values) == 0 {
		return nil
	}

	out := make([]string, 0, len(values))
	seen := make(map[string]struct{}, len(values))
	for i := range values {
		v := strings.ToLower(strings.TrimSpace(values[i]))
		v = strings.TrimPrefix(v, ".")
		if v == "" {
			continue
		}
		if _, ok := seen[v]; ok {
			continue
		}
		seen[v] = struct{}{}
		out = append(out, v)
	}
	return out
}

func loadCircuitBreakerPolicyFromEnv(prefix string, defaults CircuitBreakerPolicy) CircuitBreakerPolicy {
	return CircuitBreakerPolicy{
		Enabled:             getenvBool(prefix+"ENABLED", defaults.Enabled),
		MaxRequests:         getenvUint32(prefix+"MAX_REQUESTS", defaults.MaxRequests),
		Interval:            getenvDuration(prefix+"INTERVAL", defaults.Interval),
		BucketPeriod:        getenvDuration(prefix+"BUCKET_PERIOD", defaults.BucketPeriod),
		Timeout:             getenvDuration(prefix+"TIMEOUT", defaults.Timeout),
		MinRequests:         getenvUint32(prefix+"MIN_REQUESTS", defaults.MinRequests),
		FailureRatio:        getenvFloat64(prefix+"FAILURE_RATIO", defaults.FailureRatio),
		ConsecutiveFailures: getenvUint32(prefix+"CONSECUTIVE_FAILURES", defaults.ConsecutiveFailures),
	}
}
