// Package config holds application configuration loaded from environment variables.
package config

import (
	"fmt"
	"os"
	"strings"
	"time"
)

// Config holds all application-level configuration.
type Config struct {
	// Driver selects the database engine: "mysql" (default) or "sqlite".
	// Read from DB_DRIVER. Switching engines is a single env var change.
	Driver string

	// DSN is the database connection string.
	//   mysql:  user:password@tcp(host:port)/dbname?parseTime=true&loc=UTC&time_zone=%%27UTC%%27
	//   sqlite: a file path (e.g. /var/lib/pangolin/pangolin.db) or :memory:
	DSN string

	// RedisAddr is the Redis server address (host:port).
	RedisAddr     string
	RedisPassword string
	RedisDB       int

	// WebhookSecret is the HMAC-SHA256 shared secret for the card-store webhook.
	// Must be set; no default.
	WebhookSecret string

	// RedeemFailMax is the number of consecutive redeem failures before a 1-hour lock.
	// Default: 5
	RedeemFailMax int

	// RedeemLockDuration is how long the lock lasts after hitting RedeemFailMax.
	// Default: 1 hour
	RedeemLockDuration time.Duration

	// WebhookTimestampTolerance is the ±window for webhook timestamp validation.
	// Default: 5 minutes
	WebhookTimestampTolerance time.Duration

	// WebhookNonceTTL is how long a webhook nonce is kept in Redis to prevent replay.
	// Should be > 2 * WebhookTimestampTolerance.  Default: 15 minutes.
	WebhookNonceTTL time.Duration

	// ── Auth / JWT (RS256) ────────────────────────────────────────────────
	// JWTPrivateKeyPath is the PEM file holding the active RS256 signing key.
	JWTPrivateKeyPath string
	// JWTKeyID is the `kid` written into the JWT header (identifies the signing key).
	JWTKeyID string
	// JWTPublicKeys maps kid -> PEM public-key file path. It must include the
	// active key's kid and may carry previous keys still accepted during
	// rotation. Parsed from JWT_PUBLIC_KEYS="kid1:/path1,kid2:/path2".
	JWTPublicKeys map[string]string

	// ─── gRPC / agent server ──────────────────────────────────────────────

	// GRPCAddr is the listen address for the mTLS gRPC agent server (host:port).
	// Optional: if empty, the gRPC server is not started.
	// Example: ":9443"
	GRPCAddr string

	// CAKeyPath and CACertPath are file paths for the Pangolin Node CA.
	// Required when GRPCAddr is set.
	CAKeyPath  string
	CACertPath string

	// GRPCCertPath and GRPCKeyPath are the TLS certificate/key used by the gRPC
	// listener for its own transport credential (distinct from the node CA).
	// Required when GRPCAddr is set.
	GRPCCertPath string
	GRPCKeyPath  string
}

// FromEnv reads configuration from environment variables.
// Returns an error if required variables are missing.
func FromEnv() (*Config, error) {
	c := &Config{
		Driver:                    os.Getenv("DB_DRIVER"),
		DSN:                       os.Getenv("DB_DSN"),
		RedisAddr:                 getEnvDefault("REDIS_ADDR", "127.0.0.1:6379"),
		RedisPassword:             os.Getenv("REDIS_PASSWORD"),
		RedisDB:                   0,
		WebhookSecret:             os.Getenv("WEBHOOK_SECRET"),
		RedeemFailMax:             5,
		RedeemLockDuration:        time.Hour,
		WebhookTimestampTolerance: 5 * time.Minute,
		WebhookNonceTTL:           15 * time.Minute,
		JWTPrivateKeyPath:         os.Getenv("JWT_PRIVATE_KEY_PATH"),
		JWTKeyID:                  os.Getenv("JWT_KEY_ID"),
		JWTPublicKeys:             parseKeyMap(os.Getenv("JWT_PUBLIC_KEYS")),
		// gRPC server (optional)
		GRPCAddr:     os.Getenv("GRPC_ADDR"),
		CAKeyPath:    os.Getenv("CA_KEY_PATH"),
		CACertPath:   os.Getenv("CA_CERT_PATH"),
		GRPCCertPath: os.Getenv("GRPC_CERT_PATH"),
		GRPCKeyPath:  os.Getenv("GRPC_KEY_PATH"),
	}

	if c.DSN == "" {
		return nil, fmt.Errorf("config: DB_DSN is required")
	}
	if c.WebhookSecret == "" {
		return nil, fmt.Errorf("config: WEBHOOK_SECRET is required")
	}
	return c, nil
}

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

// parseKeyMap parses a "kid1:/path1,kid2:/path2" string into a map. Empty input
// yields a nil map. Malformed entries (missing ':') are skipped.
func parseKeyMap(raw string) map[string]string {
	if raw == "" {
		return nil
	}
	m := map[string]string{}
	for _, pair := range strings.Split(raw, ",") {
		pair = strings.TrimSpace(pair)
		if pair == "" {
			continue
		}
		i := strings.IndexByte(pair, ':')
		if i <= 0 || i == len(pair)-1 {
			continue
		}
		m[strings.TrimSpace(pair[:i])] = strings.TrimSpace(pair[i+1:])
	}
	return m
}
