package config

import (
	"os"
	"strconv"
	"strings"
	"time"
)

// Config holds all runtime configuration, loaded from environment variables
// (optionally seeded from a .env file). Secrets are never logged.
type Config struct {
	AppEnv   string
	AppPort  int
	timezone string

	DatabaseURL string
	RedisURL    string

	// AutoMigrate applies pending embedded SQL migrations (migrations/*.sql)
	// on startup via internal/dbmigrate. Default true; set AUTO_MIGRATE=false
	// (or 0) to keep boot from touching the schema.
	AutoMigrate bool

	// OpenAPIPath points at the OpenAPI document served on /openapi.yaml and
	// rendered by /docs (Scalar). Defaults to the repo's docs/openapi.yaml.
	OpenAPIPath string

	TokenTTLHours int

	AllowNegativeStock  bool
	RoutingProvider     string
	RoutingBaseURL      string
	GeocoderEnabled     bool
	GeocoderBaseURL     string
	FixPayDebtReference bool

	// IdempotencyTTLHours is how long a completed idempotency record lives in
	// Redis before replay is no longer served (business-rules §19).
	IdempotencyTTLHours int

	CORSOrigins             []string
	RateLimitLoginPerMinute int
	RateLimitGenericPerMin  int
	RateLimitTrackingPerMin int
	// TrustedProxies lists proxy IPs/CIDRs whose X-Forwarded-For values are
	// accepted. Empty (default) means XFF is never trusted and the direct
	// peer address is used (Phase 7 §1).
	TrustedProxies     []string
	BodySizeLimitBytes int64

	// UploadDir is the root directory used by the local object store for file
	// uploads (images served from /uploads/*). Defaults to ./uploads.
	UploadDir string

	LogLevel string

	// SMTP_* configure outbound email (password reset links). SMTPHost
	// empty means no SMTP server is configured: internal/mailer falls back
	// to logging the email instead of sending it (dev/CI convenience only;
	// production must set SMTP_HOST).
	SMTPHost string
	SMTPPort string
	SMTPUser string
	SMTPPass string
	SMTPFrom string

	// SupportEmail is the central support inbox copied on support ticket events.
	// Defaults to support@keluargamart.id.
	SupportEmail string

	// NextReportURL is the base URL of the NextReport engine service.
	NextReportURL string

	// B2BBaseURL is the B2B storefront's public base URL, used to build
	// links embedded in outbound email (e.g. the password reset link).
	B2BBaseURL string

	// PublicAPIBaseURL is this backend's own public origin, used to build the
	// URL Midtrans POSTs transaction updates to (X-Override-Notification on the
	// Core API charge, and the registered notification URL).
	//
	// It must be an ORIGIN the provider can reach over the public internet, not
	// an in-cluster address: `http://backend:8080` resolves for us and for
	// nothing else, and a notification the provider cannot deliver means the
	// payment settles only by polling.
	PublicAPIBaseURL string

	// MidtransServerKey/ClientKey configure Snap payments. Empty server key
	// means the gateway is disabled (snap-token returns 503, no secret leak).
	MidtransServerKey    string
	MidtransClientKey    string
	MidtransIsProduction bool
	// MidtransSnapBaseURL is derived from MidtransIsProduction (sandbox default).
	MidtransSnapBaseURL string
	// MidtransCoreBaseURL is the Core API status/cancel host (api.* family) —
	// derived from MidtransIsProduction like the Snap host. Never mix: token
	// creation lives on app.*, transaction status on api.*.
	MidtransCoreBaseURL string
	// MidtransSweepToken gates the background Snap sweeper's HTTP route
	// (POST /midtrans/sweep, driven by crontab). Empty means the sweeper is not
	// deployed and the route answers 404 rather than 503, so a scanner cannot
	// learn that this deployment runs one. It is NOT a Midtrans credential: the
	// provider never sees it, and no gateway behaviour changes when it is unset.
	MidtransSweepToken string
}

func Load() Config {
	c := Config{
		AppEnv:        getEnv("APP_ENV", "local"),
		AppPort:       getEnvInt("APP_PORT", 8080),
		timezone:      getEnv("TIMEZONE", "Asia/Jakarta"),
		DatabaseURL:   getEnv("DATABASE_URL", ""),
		RedisURL:      getEnv("REDIS_URL", ""),
		AutoMigrate:   getEnvBool("AUTO_MIGRATE", true),
		OpenAPIPath:   getEnv("OPENAPI_PATH", "docs/openapi.yaml"),
		TokenTTLHours: getEnvInt("TOKEN_TTL_HOURS", 24),
		// AllowNegativeStock enables permissive stock deductions across POS, B2B,
		// and retail sales. Defaults to true to support PT Indodes omnichannel
		// backorder and immediate cashier register checkout.
		AllowNegativeStock:      getEnvBool("ALLOW_NEGATIVE_STOCK", true),
		RoutingProvider:         getEnv("ROUTING_PROVIDER", ""),
		RoutingBaseURL:          getEnv("ROUTING_BASE_URL", ""),
		GeocoderEnabled:         getEnvBool("GEOCODER_ENABLED", false),
		GeocoderBaseURL:         getEnv("GEOCODER_BASE_URL", "https://nominatim.openstreetmap.org"),
		FixPayDebtReference:     getEnvBool("FIX_PAYDEBT_REFERENCE", true),
		IdempotencyTTLHours:     getEnvInt("IDEMPOTENCY_TTL_HOURS", 24),
		CORSOrigins:             splitCSV(getEnv("CORS_ORIGINS", "http://localhost:3000")),
		RateLimitLoginPerMinute: getEnvInt("RATE_LIMIT_LOGIN_PER_MINUTE", 10),
		RateLimitGenericPerMin:  getEnvInt("RATE_LIMIT_GENERIC_PER_MINUTE", 300),
		RateLimitTrackingPerMin: getEnvInt("RATE_LIMIT_TRACKING_PER_MINUTE", 60),
		TrustedProxies:          splitCSV(getEnv("TRUSTED_PROXIES", "")),
		BodySizeLimitBytes:      int64(getEnvInt("BODY_SIZE_LIMIT_BYTES", 10*1024*1024)),
		UploadDir:               getEnv("UPLOAD_DIR", "./uploads"),
		LogLevel:                getEnv("LOG_LEVEL", "info"),
		SMTPHost:                getEnv("SMTP_HOST", ""),
		SMTPPort:                getEnv("SMTP_PORT", "587"),
		SMTPUser:                getEnv("SMTP_USER", ""),
		SMTPPass:                getEnv("SMTP_PASS", ""),
		SMTPFrom:                getEnv("SMTP_FROM", "no-reply@keluargamart.local"),
		SupportEmail:            getEnv("SUPPORT_EMAIL", "sprtcrnbry@gmail.com"),
		NextReportURL:           getEnv("NEXTREPORT_URL", "http://localhost:5489"),
		B2BBaseURL:              getEnv("B2B_BASE_URL", "https://stg-b2b-km.trl.my.id"),
		PublicAPIBaseURL:        getEnv("PUBLIC_API_BASE_URL", "https://stg-api-km.trl.my.id"),
		MidtransServerKey:       getEnv("MIDTRANS_SERVER_KEY", ""),
		MidtransClientKey:       getEnv("MIDTRANS_CLIENT_KEY", ""),
		MidtransIsProduction:    getEnvBool("MIDTRANS_IS_PRODUCTION", false),
		MidtransSweepToken:      getEnv("MIDTRANS_SWEEP_TOKEN", ""),
	}
	c.MidtransSnapBaseURL = "https://app.sandbox.midtrans.com/snap/v1/transactions"
	c.MidtransCoreBaseURL = "https://api.sandbox.midtrans.com/v2"
	if c.MidtransIsProduction {
		c.MidtransSnapBaseURL = "https://app.midtrans.com/snap/v1/transactions"
		c.MidtransCoreBaseURL = "https://api.midtrans.com/v2"
	}

	return c
}

// Timezone returns the configured time.Location (defaults to Asia/Jakarta).
// Failure to load falls back to UTC, never panics at startup.
func (c Config) Timezone() *time.Location {
	loc, err := time.LoadLocation(c.timezone)
	if err != nil {
		return time.UTC
	}
	return loc
}

func getEnv(key, def string) string {
	if v, ok := os.LookupEnv(key); ok && v != "" {
		return v
	}
	return def
}

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

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

func splitCSV(s string) []string {
	parts := strings.Split(s, ",")
	out := make([]string, 0, len(parts))
	for _, p := range parts {
		p = strings.TrimSpace(p)
		if p != "" {
			out = append(out, p)
		}
	}
	return out
}
