package config

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"net"
	netmail "net/mail"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
	"unicode"

	"github.com/tidbcloud/AgentTeam/envcompat"
	"github.com/tidbcloud/AgentTeam/internal/core/authidentity"
	"github.com/tidbcloud/AgentTeam/internal/versioning"
)

const (
	ServerProfileAll       = "all"
	ServerProfileCentral   = "central"
	ServerProfileWorkspace = "workspace"
	// ServerProfileExtension registers only the Extension runtime plus common
	// health/observability routes (spec §6.1). It does not register Web, IM,
	// daemon WebSocket, LLM, workflow, or Workspace routes, and does not inherit
	// Workspace background workers.
	ServerProfileExtension  = "extension"
	DefaultWorkspaceGroupID = "workspace-default"

	archiveDriverFilesystem    = "filesystem"
	archiveDriverS3            = "s3"
	mediaStorageDriverDisabled = "disabled"
	mediaStorageDriverS3       = "s3"
	mediaStorageDriverOSS      = "oss"
	mailDriverNoop             = "noop"
	mailDriverSES              = "ses"
	mobileVerifyDriverNoop     = "noop"
	mobileVerifyDriverTwilio   = "twilio"
	mailDriverSMTP             = "smtp"
	mailSMTPSecuritySSL        = "ssl"
	mailSMTPSecuritySTARTTLS   = "starttls"
	registrationModeOpen       = "open"
	registrationModeInviteOnly = "invite_only"

	defaultOrgCredentialsKeyFile = ".data/secrets/org_credentials_master_key"
	defaultAppCredentialsKeyFile = ".data/secrets/app_credentials_master_key"
)

// Config is the complete runtime configuration for the service.
type Config struct {
	Environment    string
	Log            LogConfig
	PublicBaseURL  string
	Server         ServerConfig
	Web            WebConfig
	Teams          TeamsConfig
	AgentTools     AgentToolsConfig
	Organization   OrganizationConfig
	Billing        BillingConfig
	HTTP           HTTPConfig
	Observability  ObservabilityConfig
	Database       DatabaseConfig
	LLM            LLMConfig
	Archive        ArchiveConfig
	MediaStorage   MediaStorageConfig
	Mail           MailConfig
	MobileVerify   MobileVerifyConfig
	Auth           AuthConfig
	Messages       MessagesConfig
	UnreadRuntime  UnreadRuntimeConfig
	MeshX          MeshXConfig
	Daemon         DaemonConfig
	Feishu         FeishuConfig
	AppCredentials AppCredentialsConfig
	PingCAPAuth    PingCAPAuthConfig
}

// ServerConfig selects which deployment profile this process serves.
type ServerConfig struct {
	Profile                         string
	WorkspaceGroupID                string
	DefaultWorkspaceGroupID         string
	InternalAdminToken              string
	AppNotificationInternalServices []AppNotificationInternalServiceConfig
}

// AppNotificationInternalServiceConfig registers one narrowly-authorized
// first-party service identity for the trusted App Notification ingress.
type AppNotificationInternalServiceConfig struct {
	IdentityID                string   `json:"identityId"`
	Secret                    string   `json:"secret"`
	ServiceDefinitionSlug     string   `json:"serviceDefinitionSlug"`
	AllowedBusinessNamespaces []string `json:"allowedBusinessNamespaces"`
	AllowedTargetKinds        []string `json:"allowedTargetKinds"`
	AllowedContentKinds       []string `json:"allowedContentKinds"`
}

// HTTPConfig controls the public HTTP listener.
type HTTPConfig struct {
	Address                 string
	ShutdownTimeout         time.Duration
	ReadyzDependencyTimeout time.Duration
	StartupSchemaTimeout    time.Duration
}

const DefaultReadyzDependencyTimeout = 2 * time.Second
const DefaultStartupSchemaTimeout = 2 * time.Minute

func (cfg HTTPConfig) EffectiveReadyzDependencyTimeout() time.Duration {
	if cfg.ReadyzDependencyTimeout == 0 {
		return DefaultReadyzDependencyTimeout
	}
	return cfg.ReadyzDependencyTimeout
}

func (cfg HTTPConfig) EffectiveStartupSchemaTimeout() time.Duration {
	if cfg.StartupSchemaTimeout == 0 {
		return DefaultStartupSchemaTimeout
	}
	return cfg.StartupSchemaTimeout
}

// WebConfig controls optional server-side static web serving.
type WebConfig struct {
	ServeWeb bool
	DistDir  string
}

// ObservabilityConfig controls metrics and tracing surfaces.
type ObservabilityConfig struct {
	MetricsAddress     string
	OTLPEndpoint       string
	OTLPHeaders        map[string]string
	TraceSampleRatio   float64
	DBStatementEnabled bool
}

// TeamsConfig controls the browser Team management entry points.
type TeamsConfig struct {
	Enabled bool
}

// AgentToolsConfig controls hidden developer UI for agent tool settings.
type AgentToolsConfig struct {
	SettingsEnabled bool
}

// OrganizationConfig controls organization subfeature availability.
type OrganizationConfig struct {
	DirectoryEnabled bool
	BYOKEnabled      bool
}

// BillingConfig controls billing UI availability.
type BillingConfig struct {
	Enabled       bool
	ReturnBaseURL string
	Alipay        AlipayConfig
}

// AlipayConfig controls the Alipay OpenAPI integration used by billing.
type AlipayConfig struct {
	AppID           string
	AppPrivateKey   string
	AlipayPublicKey string
	GatewayURL      string
	RequestTimeout  time.Duration
}

// DatabaseConfig controls the core main-path database access.
type DatabaseConfig struct {
	URL            string
	ConnectTimeout time.Duration
	Pool           DatabasePoolConfig
}

// DatabasePoolConfig controls the process-wide sql.DB connection pool.
type DatabasePoolConfig struct {
	MaxOpenConns    int
	MaxIdleConns    int
	ConnMaxLifetime time.Duration
	ConnMaxIdleTime time.Duration
}

// LLMConfig controls process-wide LLM runtime settings.
type LLMConfig struct {
	OutboundProxy                string
	MaaSBaseURL                  string
	MaaSRuntimeBaseURL           string
	MaaSInternalBearerToken      string
	MaaSCapabilityPrivateKeyFile string
}

// ArchiveConfig selects where cold history is stored.
type ArchiveConfig struct {
	Driver         string
	FilesystemRoot string
	S3Region       string
	S3Bucket       string
	S3Prefix       string
}

// MediaStorageConfig selects where media payloads are stored.
type MediaStorageConfig struct {
	Driver          string
	Region          string
	Bucket          string
	Prefix          string
	Endpoint        string
	PublicBaseURL   string
	AccessKeyID     string
	AccessKeySecret string
}

// MailConfig selects how email is sent.
type MailConfig struct {
	Driver       string
	FromAddress  string
	SESRegion    string
	SMTPHost     string
	SMTPPort     int
	SMTPUsername string
	SMTPPassword string
	SMTPSecurity string
}

// MobileVerifyConfig selects how mobile verification codes are delivered.
type MobileVerifyConfig struct {
	Driver                    string
	TwilioAccountSID          string
	TwilioAuthToken           string
	TwilioFromNumber          string
	TwilioMessagingServiceSID string
}

// AuthConfig holds auth-related TTLs.
type AuthConfig struct {
	VerificationTokenTTL        time.Duration
	SessionTTL                  time.Duration
	RegistrationMode            string
	RegistrationAllowlistEmails []string
	// PasswordAuthDisabled is derived from AGENTTEAM_PASSWORD_AUTH_ENABLED=false.
	// The inverted field keeps zero-value AuthConfig compatible with password auth enabled.
	PasswordAuthDisabled bool
}

// MessagesConfig keeps the server-side history retention knobs reserved for future policy work.
type MessagesConfig struct {
	// HotRetentionDays is still loaded from env for compatibility, but the current implementation
	// keeps all `scope_messages` rows visible to public history reads and disables automatic archival.
	HotRetentionDays int
}

// UnreadRuntimeConfig controls owner-local unread runtime recovery snapshots.
type UnreadRuntimeConfig struct {
	SnapshotFlushInterval  time.Duration
	SnapshotFlushBatchSize int
	HydrateMaxConcurrency  int
	HydrateBatchSize       int
}

// MeshXConfig controls the MeshX product surface. Process topology is owned by
// ServerConfig.Profile: enabled all/workspace profiles compose MeshX in process,
// while central never constructs it.
type MeshXConfig struct {
	Enabled               bool
	KnowledgeBasesEnabled bool
	WorkflowsEnabled      bool
	MiniappPairingEnabled bool
	RequestTimeout        time.Duration
	ServiceTokenTTL       time.Duration
}

// DaemonConfig controls how the service discovers the latest daemon version.
type DaemonConfig struct {
	// ConnectBaseURL is the externally reachable base URL that UI should embed in daemon connect commands.
	ConnectBaseURL string
	// LatestVersionURL is the CDN stable manifest URL used to discover the recommended AgentTeam daemon version.
	LatestVersionURL string
	// LatestVersionOverride forces a fixed latest daemon version and disables manifest lookups.
	LatestVersionOverride string
	// MinDaemonVersion enables the hard minimum daemon version gate for agent message routes when set.
	MinDaemonVersion string
	// LatestVersionCacheTTL controls how long the latest version is cached in memory.
	LatestVersionCacheTTL time.Duration
}

// FeishuConfig carries server-side Feishu application credentials.
type FeishuConfig struct {
	AppID                   string
	AppSecret               string
	OrgCredentialsMasterKey string
	OrgCredentialsKeyFile   string
}

// AppCredentialsConfig carries the deployment-level key for App and agent runtime secrets.
type AppCredentialsConfig struct {
	MasterKey string
	KeyFile   string
}

// PingCAPAuthConfig carries PingCAP User Center OIDC configuration.
type PingCAPAuthConfig struct {
	Enabled             bool
	Issuer              string
	ClientID            string
	ClientSecret        string
	WebClientID         string
	WebClientSecret     string
	DesktopClientID     string
	DesktopClientSecret string
}

func (cfg PingCAPAuthConfig) webClientIDOrLegacy() string {
	if strings.TrimSpace(cfg.WebClientID) != "" {
		return strings.TrimSpace(cfg.WebClientID)
	}
	return strings.TrimSpace(cfg.ClientID)
}

// LoadFromEnv parses environment variables into a validated Config.
func LoadFromEnv() (Config, error) {
	appNotificationInternalServices, err := parseAppNotificationInternalServices(os.Getenv("AGENTTEAM_APP_NOTIFICATION_INTERNAL_SERVICES_JSON"))
	if err != nil {
		return Config{}, err
	}
	environment := envOrDefault("AGENTTEAM_ENV", "development")
	logConfig, err := loadLogConfigFromEnv()
	if err != nil {
		return Config{}, err
	}
	serverProfile, err := serverProfileFromEnv()
	if err != nil {
		return Config{}, err
	}
	httpShutdownTimeout, err := durationOrDefault("AGENTTEAM_HTTP_SHUTDOWN_TIMEOUT", 10*time.Second)
	if err != nil {
		return Config{}, err
	}
	readyzDependencyTimeout, err := durationOrDefault("AGENTTEAM_READYZ_DEPENDENCY_TIMEOUT", 2*time.Second)
	if err != nil {
		return Config{}, err
	}
	if readyzDependencyTimeout <= 0 {
		return Config{}, fmt.Errorf("AGENTTEAM_READYZ_DEPENDENCY_TIMEOUT must be positive")
	}
	startupSchemaTimeout, err := durationOrDefault("AGENTTEAM_STARTUP_SCHEMA_TIMEOUT", DefaultStartupSchemaTimeout)
	if err != nil {
		return Config{}, err
	}
	if startupSchemaTimeout <= 0 {
		return Config{}, fmt.Errorf("AGENTTEAM_STARTUP_SCHEMA_TIMEOUT must be positive")
	}
	traceSampleRatio, err := floatOrDefault("AGENTTEAM_OBSERVABILITY_TRACE_SAMPLE_RATIO", defaultTraceSampleRatio(environment))
	if err != nil {
		return Config{}, err
	}
	dbStatementEnabled, err := boolEnvOrDefault("AGENTTEAM_OBSERVABILITY_DB_STATEMENT_ENABLED", false)
	if err != nil {
		return Config{}, err
	}
	otlpHeaders, err := otlpHeadersEnv("AGENTTEAM_OBSERVABILITY_OTLP_HEADERS")
	if err != nil {
		return Config{}, err
	}
	databaseConnectTimeout, err := durationOrDefault("AGENTTEAM_DATABASE_CONNECT_TIMEOUT", 5*time.Second)
	if err != nil {
		return Config{}, err
	}
	databaseMaxOpenConns, maxOpenConnsSet, err := optionalIntEnv("AGENTTEAM_DATABASE_MAX_OPEN_CONNS")
	if err != nil {
		return Config{}, err
	}
	databaseMaxIdleConns, maxIdleConnsSet, err := optionalIntEnv("AGENTTEAM_DATABASE_MAX_IDLE_CONNS")
	if err != nil {
		return Config{}, err
	}
	databaseConnMaxLifetime, err := durationOrDefault("AGENTTEAM_DATABASE_CONN_MAX_LIFETIME", 0)
	if err != nil {
		return Config{}, err
	}
	databaseConnMaxIdleTime, err := durationOrDefault("AGENTTEAM_DATABASE_CONN_MAX_IDLE_TIME", 0)
	if err != nil {
		return Config{}, err
	}
	verificationTokenTTL, err := durationOrDefault("AGENTTEAM_AUTH_VERIFICATION_TOKEN_TTL", 24*time.Hour)
	if err != nil {
		return Config{}, err
	}
	sessionTTL, err := durationOrDefault("AGENTTEAM_AUTH_SESSION_TTL", 30*24*time.Hour)
	if err != nil {
		return Config{}, err
	}
	passwordAuthEnabled, err := boolEnvOrDefault("AGENTTEAM_PASSWORD_AUTH_ENABLED", true)
	if err != nil {
		return Config{}, err
	}
	agentToolsSettingsEnabled, err := boolEnvOrDefault("AGENTTEAM_AGENT_TOOLS_SETTINGS_ENABLED", true)
	if err != nil {
		return Config{}, err
	}
	organizationDirectoryEnabled, err := boolEnvOrDefault("AGENTTEAM_ORGANIZATION_DIRECTORY_ENABLED", true)
	if err != nil {
		return Config{}, err
	}
	organizationBYOKEnabled, err := boolEnvOrDefault("AGENTTEAM_ORGANIZATION_BYOK_ENABLED", true)
	if err != nil {
		return Config{}, err
	}
	billingEnabled, err := boolEnvOrDefault("AGENTTEAM_BILLING_ENABLED", false)
	if err != nil {
		return Config{}, err
	}
	alipayRequestTimeout, err := durationOrDefault("AGENTTEAM_ALIPAY_REQUEST_TIMEOUT", 15*time.Second)
	if err != nil {
		return Config{}, err
	}
	hotMessageRetentionDays, err := envcompat.Int("AGENTTEAM_MESSAGES_RETENTION_DAYS", 30, "AGENTTEAM_MESSAGE_HISTORY_DAYS")
	if err != nil {
		return Config{}, err
	}
	daemonLatestVersionCacheTTL, err := durationOrDefault("AGENTTEAM_DAEMON_LATEST_VERSION_CACHE_TTL", 6*time.Hour)
	if err != nil {
		return Config{}, err
	}
	unreadSnapshotFlushInterval, err := durationOrDefault("AGENTTEAM_UNREAD_RUNTIME_SNAPSHOT_FLUSH_INTERVAL", 30*time.Second)
	if err != nil {
		return Config{}, err
	}
	if unreadSnapshotFlushInterval <= 0 {
		return Config{}, fmt.Errorf("AGENTTEAM_UNREAD_RUNTIME_SNAPSHOT_FLUSH_INTERVAL must be positive")
	}
	unreadSnapshotFlushBatchSize, err := intOrDefault("AGENTTEAM_UNREAD_RUNTIME_SNAPSHOT_FLUSH_BATCH_SIZE", 1000)
	if err != nil {
		return Config{}, err
	}
	if unreadSnapshotFlushBatchSize <= 0 {
		return Config{}, fmt.Errorf("AGENTTEAM_UNREAD_RUNTIME_SNAPSHOT_FLUSH_BATCH_SIZE must be positive")
	}
	unreadHydrateMaxConcurrency, err := intOrDefault("AGENTTEAM_UNREAD_RUNTIME_HYDRATE_MAX_CONCURRENCY", 2)
	if err != nil {
		return Config{}, err
	}
	if unreadHydrateMaxConcurrency <= 0 {
		return Config{}, fmt.Errorf("AGENTTEAM_UNREAD_RUNTIME_HYDRATE_MAX_CONCURRENCY must be positive")
	}
	unreadHydrateBatchSize, err := intOrDefault("AGENTTEAM_UNREAD_RUNTIME_HYDRATE_BATCH_SIZE", 100)
	if err != nil {
		return Config{}, err
	}
	if unreadHydrateBatchSize <= 0 {
		return Config{}, fmt.Errorf("AGENTTEAM_UNREAD_RUNTIME_HYDRATE_BATCH_SIZE must be positive")
	}
	meshXRequestTimeout, err := durationOrDefault("AGENTTEAM_MESHX_REQUEST_TIMEOUT", 30*time.Second)
	if err != nil {
		return Config{}, err
	}
	meshXServiceTokenTTL, err := durationOrDefault("AGENTTEAM_MESHX_SERVICE_TOKEN_TTL", time.Minute)
	if err != nil {
		return Config{}, err
	}
	meshXEnabled, err := boolEnvOrDefault("AGENTTEAM_MESHX_ENABLED", false)
	if err != nil {
		return Config{}, err
	}
	meshXKnowledgeBasesEnabled, err := boolEnvOrDefault("AGENTTEAM_MESHX_KNOWLEDGE_BASES_ENABLED", true)
	if err != nil {
		return Config{}, err
	}
	meshXWorkflowsEnabled, err := boolEnvOrDefault("AGENTTEAM_WORKFLOWS_ENABLED", true)
	if err != nil {
		return Config{}, err
	}
	meshXMiniappPairingEnabled, err := boolEnvOrDefault("AGENTTEAM_MINIAPP_PAIRING_ENABLED", true)
	if err != nil {
		return Config{}, err
	}
	serveWeb, err := envcompat.Bool("AGENTTEAM_WEB_SERVE_ENABLED", false, "AGENTTEAM_SERVE_WEB")
	if err != nil {
		return Config{}, err
	}
	smtpPort, err := intOrDefault("AGENTTEAM_MAIL_SMTP_PORT", 587)
	if err != nil {
		return Config{}, err
	}

	cfg := Config{
		Environment:   environment,
		Log:           logConfig,
		PublicBaseURL: envOrDefault("AGENTTEAM_PUBLIC_BASE_URL", "http://localhost:3001"),
		Server: ServerConfig{
			Profile:                         serverProfile,
			WorkspaceGroupID:                strings.TrimSpace(os.Getenv("AGENTTEAM_WORKSPACE_GROUP_ID")),
			DefaultWorkspaceGroupID:         envOrDefault("AGENTTEAM_DEFAULT_WORKSPACE_GROUP_ID", DefaultWorkspaceGroupID),
			InternalAdminToken:              strings.TrimSpace(os.Getenv("AGENTTEAM_INTERNAL_ADMIN_TOKEN")),
			AppNotificationInternalServices: appNotificationInternalServices,
		},
		Web: WebConfig{
			ServeWeb: serveWeb,
			DistDir:  strings.TrimSpace(os.Getenv("AGENTTEAM_WEB_DIST_DIR")),
		},
		Teams: TeamsConfig{
			Enabled: true,
		},
		AgentTools: AgentToolsConfig{
			SettingsEnabled: agentToolsSettingsEnabled,
		},
		Organization: OrganizationConfig{
			DirectoryEnabled: organizationDirectoryEnabled,
			BYOKEnabled:      organizationBYOKEnabled,
		},
		Billing: BillingConfig{
			Enabled:       billingEnabled,
			ReturnBaseURL: strings.TrimSpace(os.Getenv("AGENTTEAM_BILLING_RETURN_BASE_URL")),
			Alipay: AlipayConfig{
				AppID:           strings.TrimSpace(os.Getenv("AGENTTEAM_ALIPAY_APP_ID")),
				AppPrivateKey:   strings.TrimSpace(os.Getenv("AGENTTEAM_ALIPAY_APP_PRIVATE_KEY")),
				AlipayPublicKey: strings.TrimSpace(os.Getenv("AGENTTEAM_ALIPAY_PUBLIC_KEY")),
				GatewayURL:      strings.TrimSpace(os.Getenv("AGENTTEAM_ALIPAY_GATEWAY_URL")),
				RequestTimeout:  alipayRequestTimeout,
			},
		},
		HTTP: HTTPConfig{
			Address:                 envOrDefault("AGENTTEAM_HTTP_ADDRESS", ":3001"),
			ShutdownTimeout:         httpShutdownTimeout,
			ReadyzDependencyTimeout: readyzDependencyTimeout,
			StartupSchemaTimeout:    startupSchemaTimeout,
		},
		Observability: ObservabilityConfig{
			MetricsAddress:     envOrDefaultAllowExplicitEmpty("AGENTTEAM_OBSERVABILITY_METRICS_ADDRESS", "127.0.0.1:2112"),
			OTLPEndpoint:       strings.TrimSpace(os.Getenv("AGENTTEAM_OBSERVABILITY_OTLP_ENDPOINT")),
			OTLPHeaders:        otlpHeaders,
			TraceSampleRatio:   traceSampleRatio,
			DBStatementEnabled: dbStatementEnabled,
		},
		Database: DatabaseConfig{
			URL:            coreStorageDSNFromEnv(),
			ConnectTimeout: databaseConnectTimeout,
			Pool: DatabasePoolConfig{
				MaxOpenConns:    resolvedDatabaseMaxOpenConns(databaseMaxOpenConns, maxOpenConnsSet),
				MaxIdleConns:    resolvedDatabaseMaxIdleConns(databaseMaxOpenConns, maxOpenConnsSet, databaseMaxIdleConns, maxIdleConnsSet),
				ConnMaxLifetime: databaseConnMaxLifetime,
				ConnMaxIdleTime: databaseConnMaxIdleTime,
			},
		},
		LLM: LLMConfig{
			OutboundProxy:                strings.TrimSpace(os.Getenv("AGENTTEAM_LLM_OUTBOUND_PROXY")),
			MaaSBaseURL:                  strings.TrimSpace(os.Getenv("AGENTTEAM_MAAS_BASE_URL")),
			MaaSRuntimeBaseURL:           strings.TrimSpace(os.Getenv("AGENTTEAM_MAAS_RUNTIME_BASE_URL")),
			MaaSInternalBearerToken:      strings.TrimSpace(os.Getenv("AGENTTEAM_MAAS_INTERNAL_BEARER_TOKEN")),
			MaaSCapabilityPrivateKeyFile: strings.TrimSpace(os.Getenv("AGENTTEAM_MAAS_CAPABILITY_ED25519_PRIVATE_KEY_FILE")),
		},
		Archive: ArchiveConfig{
			Driver:         envOrDefault("AGENTTEAM_ARCHIVE_DRIVER", archiveDriverFilesystem),
			FilesystemRoot: envOrDefault("AGENTTEAM_ARCHIVE_FILESYSTEM_ROOT", ".data/archive"),
			S3Region:       strings.TrimSpace(os.Getenv("AGENTTEAM_ARCHIVE_S3_REGION")),
			S3Bucket:       strings.TrimSpace(os.Getenv("AGENTTEAM_ARCHIVE_S3_BUCKET")),
			S3Prefix:       envOrDefault("AGENTTEAM_ARCHIVE_S3_PREFIX", "agentteam/archive"),
		},
		MediaStorage: MediaStorageConfig{
			Driver:          strings.ToLower(strings.TrimSpace(envOrDefault("AGENTTEAM_MEDIA_OBJECT_STORAGE_DRIVER", mediaStorageDriverDisabled))),
			Region:          firstNonEmptyEnv("AGENTTEAM_MEDIA_OSS_REGION", "AGENTTEAM_MEDIA_S3_REGION"),
			Bucket:          firstNonEmptyEnv("AGENTTEAM_MEDIA_OSS_BUCKET", "AGENTTEAM_MEDIA_S3_BUCKET"),
			Prefix:          firstNonEmptyEnvOrDefault("agentteam/objects", "AGENTTEAM_MEDIA_OSS_PREFIX", "AGENTTEAM_MEDIA_S3_PREFIX"),
			Endpoint:        firstNonEmptyEnv("AGENTTEAM_MEDIA_OSS_ENDPOINT", "AGENTTEAM_MEDIA_S3_ENDPOINT"),
			PublicBaseURL:   strings.TrimSpace(os.Getenv("AGENTTEAM_MEDIA_PUBLIC_BASE_URL")),
			AccessKeyID:     firstNonEmptyEnv("AGENTTEAM_MEDIA_OSS_ACCESS_KEY_ID", "AGENTTEAM_MEDIA_S3_ACCESS_KEY_ID"),
			AccessKeySecret: firstNonEmptyEnv("AGENTTEAM_MEDIA_OSS_ACCESS_KEY_SECRET", "AGENTTEAM_MEDIA_S3_ACCESS_KEY_SECRET"),
		},
		Mail: MailConfig{
			Driver:       envOrDefault("AGENTTEAM_MAIL_DRIVER", mailDriverNoop),
			FromAddress:  strings.TrimSpace(os.Getenv("AGENTTEAM_MAIL_FROM_ADDRESS")),
			SESRegion:    strings.TrimSpace(os.Getenv("AGENTTEAM_MAIL_SES_REGION")),
			SMTPHost:     strings.TrimSpace(os.Getenv("AGENTTEAM_MAIL_SMTP_HOST")),
			SMTPPort:     smtpPort,
			SMTPUsername: strings.TrimSpace(os.Getenv("AGENTTEAM_MAIL_SMTP_USERNAME")),
			SMTPPassword: strings.TrimSpace(os.Getenv("AGENTTEAM_MAIL_SMTP_PASSWORD")),
			SMTPSecurity: strings.ToLower(strings.TrimSpace(envOrDefault("AGENTTEAM_MAIL_SMTP_SECURITY", mailSMTPSecuritySTARTTLS))),
		},
		MobileVerify: MobileVerifyConfig{
			Driver:                    envOrDefault("AGENTTEAM_MOBILE_VERIFY_DRIVER", mobileVerifyDriverNoop),
			TwilioAccountSID:          strings.TrimSpace(os.Getenv("AGENTTEAM_MOBILE_VERIFY_TWILIO_ACCOUNT_SID")),
			TwilioAuthToken:           strings.TrimSpace(os.Getenv("AGENTTEAM_MOBILE_VERIFY_TWILIO_AUTH_TOKEN")),
			TwilioFromNumber:          strings.TrimSpace(os.Getenv("AGENTTEAM_MOBILE_VERIFY_TWILIO_FROM_NUMBER")),
			TwilioMessagingServiceSID: strings.TrimSpace(os.Getenv("AGENTTEAM_MOBILE_VERIFY_TWILIO_MESSAGING_SERVICE_SID")),
		},
		Auth: AuthConfig{
			VerificationTokenTTL:        verificationTokenTTL,
			SessionTTL:                  sessionTTL,
			RegistrationMode:            strings.ToLower(strings.TrimSpace(envOrDefault("AGENTTEAM_AUTH_REGISTRATION_MODE", registrationModeInviteOnly))),
			RegistrationAllowlistEmails: csvEnv("AGENTTEAM_AUTH_REGISTRATION_ALLOWLIST_EMAILS"),
			PasswordAuthDisabled:        !passwordAuthEnabled,
		},
		Messages: MessagesConfig{
			HotRetentionDays: hotMessageRetentionDays,
		},
		UnreadRuntime: UnreadRuntimeConfig{
			SnapshotFlushInterval:  unreadSnapshotFlushInterval,
			SnapshotFlushBatchSize: unreadSnapshotFlushBatchSize,
			HydrateMaxConcurrency:  unreadHydrateMaxConcurrency,
			HydrateBatchSize:       unreadHydrateBatchSize,
		},
		MeshX: MeshXConfig{
			Enabled:               meshXEnabled,
			KnowledgeBasesEnabled: meshXKnowledgeBasesEnabled,
			WorkflowsEnabled:      meshXWorkflowsEnabled,
			MiniappPairingEnabled: meshXMiniappPairingEnabled,
			RequestTimeout:        meshXRequestTimeout,
			ServiceTokenTTL:       meshXServiceTokenTTL,
		},
		Daemon: DaemonConfig{
			ConnectBaseURL:        strings.TrimSpace(os.Getenv("AGENTTEAM_DAEMON_CONNECT_BASE_URL")),
			LatestVersionURL:      envOrDefault("AGENTTEAM_DAEMON_LATEST_VERSION_URL", versioning.DefaultStableManifestURL),
			LatestVersionOverride: strings.TrimSpace(os.Getenv("AGENTTEAM_DAEMON_LATEST_VERSION_OVERRIDE")),
			MinDaemonVersion:      strings.TrimSpace(os.Getenv("AGENTTEAM_DAEMON_MIN_VERSION")),
			LatestVersionCacheTTL: daemonLatestVersionCacheTTL,
		},
		Feishu: FeishuConfig{
			AppID:                   strings.TrimSpace(os.Getenv("AGENTTEAM_FEISHU_APP_ID")),
			AppSecret:               strings.TrimSpace(os.Getenv("AGENTTEAM_FEISHU_APP_SECRET")),
			OrgCredentialsMasterKey: strings.TrimSpace(os.Getenv("AGENTTEAM_ORG_CREDENTIALS_MASTER_KEY")),
			OrgCredentialsKeyFile:   envOrDefault("AGENTTEAM_ORG_CREDENTIALS_KEY_FILE", defaultOrgCredentialsKeyFile),
		},
		AppCredentials: AppCredentialsConfig{
			MasterKey: strings.TrimSpace(os.Getenv("AGENTTEAM_APP_CREDENTIALS_MASTER_KEY")),
			KeyFile:   envOrDefault("AGENTTEAM_APP_CREDENTIALS_KEY_FILE", defaultAppCredentialsKeyFile),
		},
		PingCAPAuth: PingCAPAuthConfig{
			Enabled:             strings.ToLower(strings.TrimSpace(os.Getenv("AGENTTEAM_PINGCAP_AUTH_ENABLED"))) == "true",
			Issuer:              strings.TrimSpace(os.Getenv("AGENTTEAM_PINGCAP_AUTH_ISSUER")),
			ClientID:            strings.TrimSpace(os.Getenv("AGENTTEAM_PINGCAP_AUTH_CLIENT_ID")),
			ClientSecret:        strings.TrimSpace(os.Getenv("AGENTTEAM_PINGCAP_AUTH_CLIENT_SECRET")),
			WebClientID:         strings.TrimSpace(os.Getenv("AGENTTEAM_PINGCAP_AUTH_WEB_CLIENT_ID")),
			WebClientSecret:     strings.TrimSpace(os.Getenv("AGENTTEAM_PINGCAP_AUTH_WEB_CLIENT_SECRET")),
			DesktopClientID:     strings.TrimSpace(os.Getenv("AGENTTEAM_PINGCAP_AUTH_DESKTOP_CLIENT_ID")),
			DesktopClientSecret: strings.TrimSpace(os.Getenv("AGENTTEAM_PINGCAP_AUTH_DESKTOP_CLIENT_SECRET")),
		},
	}

	if err := cfg.Validate(); err != nil {
		return Config{}, err
	}

	return cfg, nil
}

func parseAppNotificationInternalServices(raw string) ([]AppNotificationInternalServiceConfig, error) {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return nil, nil
	}
	var services []AppNotificationInternalServiceConfig
	if err := json.Unmarshal([]byte(trimmed), &services); err != nil {
		return nil, fmt.Errorf("parse AGENTTEAM_APP_NOTIFICATION_INTERNAL_SERVICES_JSON: %w", err)
	}
	seenIdentity := map[string]struct{}{}
	seenSecret := map[string]struct{}{}
	for index := range services {
		service := &services[index]
		service.IdentityID = strings.TrimSpace(service.IdentityID)
		service.Secret = strings.TrimSpace(service.Secret)
		service.ServiceDefinitionSlug = strings.TrimSpace(service.ServiceDefinitionSlug)
		service.AllowedBusinessNamespaces = normalizedConfigStrings(service.AllowedBusinessNamespaces)
		service.AllowedTargetKinds = normalizedConfigStrings(service.AllowedTargetKinds)
		service.AllowedContentKinds = normalizedConfigStrings(service.AllowedContentKinds)
		if service.IdentityID == "" || service.Secret == "" || service.ServiceDefinitionSlug == "" || len(service.AllowedTargetKinds) == 0 || len(service.AllowedContentKinds) == 0 {
			return nil, fmt.Errorf("AGENTTEAM_APP_NOTIFICATION_INTERNAL_SERVICES_JSON entry %d requires identityId, secret, serviceDefinitionSlug, allowedTargetKinds, and allowedContentKinds", index)
		}
		if strings.IndexFunc(service.Secret, func(value rune) bool { return unicode.IsSpace(value) || unicode.IsControl(value) }) >= 0 {
			return nil, fmt.Errorf("AGENTTEAM_APP_NOTIFICATION_INTERNAL_SERVICES_JSON entry %d secret must be a single HTTP token", index)
		}
		if _, duplicate := seenIdentity[service.IdentityID]; duplicate {
			return nil, fmt.Errorf("AGENTTEAM_APP_NOTIFICATION_INTERNAL_SERVICES_JSON contains duplicate identityId %q", service.IdentityID)
		}
		if _, duplicate := seenSecret[service.Secret]; duplicate {
			return nil, fmt.Errorf("AGENTTEAM_APP_NOTIFICATION_INTERNAL_SERVICES_JSON contains a duplicate service secret")
		}
		seenIdentity[service.IdentityID] = struct{}{}
		seenSecret[service.Secret] = struct{}{}
	}
	return services, nil
}

func normalizedConfigStrings(values []string) []string {
	seen := map[string]struct{}{}
	result := make([]string, 0, len(values))
	for _, value := range values {
		trimmed := strings.TrimSpace(value)
		if trimmed == "" {
			continue
		}
		if _, ok := seen[trimmed]; ok {
			continue
		}
		seen[trimmed] = struct{}{}
		result = append(result, trimmed)
	}
	return result
}

// Validate checks whether the configuration is internally consistent.
func (cfg Config) Validate() error {
	if err := cfg.Log.Validate(); err != nil {
		return err
	}
	if err := cfg.Server.Validate(); err != nil {
		return err
	}
	if cfg.Database.URL == "" {
		return fmt.Errorf("AGENTTEAM_DATABASE_DSN is required")
	}

	switch cfg.Archive.Driver {
	case archiveDriverFilesystem:
		if cfg.Archive.FilesystemRoot == "" {
			return fmt.Errorf("AGENTTEAM_ARCHIVE_FILESYSTEM_ROOT is required when archive driver is filesystem")
		}
	case archiveDriverS3:
		if cfg.Archive.S3Region == "" {
			return fmt.Errorf("AGENTTEAM_ARCHIVE_S3_REGION is required when archive driver is s3")
		}
		if cfg.Archive.S3Bucket == "" {
			return fmt.Errorf("AGENTTEAM_ARCHIVE_S3_BUCKET is required when archive driver is s3")
		}
	default:
		return fmt.Errorf("unsupported archive driver %q", cfg.Archive.Driver)
	}

	switch cfg.MediaStorage.Driver {
	case "", mediaStorageDriverDisabled:
	case mediaStorageDriverOSS, mediaStorageDriverS3:
		if cfg.MediaStorage.Region == "" {
			return fmt.Errorf("AGENTTEAM_MEDIA_OSS_REGION is required when media object storage driver is oss")
		}
		if cfg.MediaStorage.Bucket == "" {
			return fmt.Errorf("AGENTTEAM_MEDIA_OSS_BUCKET is required when media object storage driver is oss")
		}
		if cfg.MediaStorage.Endpoint == "" {
			return fmt.Errorf("AGENTTEAM_MEDIA_OSS_ENDPOINT is required when media object storage driver is oss")
		}
		if cfg.MediaStorage.AccessKeyID == "" {
			return fmt.Errorf("AGENTTEAM_MEDIA_OSS_ACCESS_KEY_ID is required when media object storage driver is oss")
		}
		if cfg.MediaStorage.AccessKeySecret == "" {
			return fmt.Errorf("AGENTTEAM_MEDIA_OSS_ACCESS_KEY_SECRET is required when media object storage driver is oss")
		}
	default:
		return fmt.Errorf("unsupported media object storage driver %q", cfg.MediaStorage.Driver)
	}
	if err := validateBaseURL(cfg.MediaStorage.PublicBaseURL, "AGENTTEAM_MEDIA_PUBLIC_BASE_URL", strings.EqualFold(cfg.Environment, "production")); err != nil {
		return err
	}

	switch cfg.Mail.Driver {
	case mailDriverNoop:
	case mailDriverSES:
		if err := validatePlainMailAddress(cfg.Mail.FromAddress, mailDriverSES); err != nil {
			return err
		}
		if cfg.Mail.SESRegion == "" {
			return fmt.Errorf("AGENTTEAM_MAIL_SES_REGION is required when mail driver is ses")
		}
	case mailDriverSMTP:
		if err := validatePlainMailAddress(cfg.Mail.FromAddress, mailDriverSMTP); err != nil {
			return err
		}
		if cfg.Mail.SMTPHost == "" {
			return fmt.Errorf("AGENTTEAM_MAIL_SMTP_HOST is required when mail driver is smtp")
		}
		if cfg.Mail.SMTPPort <= 0 || cfg.Mail.SMTPPort > 65535 {
			return fmt.Errorf("AGENTTEAM_MAIL_SMTP_PORT must be between 1 and 65535 when mail driver is smtp")
		}
		if cfg.Mail.SMTPUsername == "" {
			return fmt.Errorf("AGENTTEAM_MAIL_SMTP_USERNAME is required when mail driver is smtp")
		}
		if cfg.Mail.SMTPPassword == "" {
			return fmt.Errorf("AGENTTEAM_MAIL_SMTP_PASSWORD is required when mail driver is smtp")
		}
		switch strings.ToLower(strings.TrimSpace(cfg.Mail.SMTPSecurity)) {
		case mailSMTPSecuritySSL, mailSMTPSecuritySTARTTLS:
		default:
			return fmt.Errorf("AGENTTEAM_MAIL_SMTP_SECURITY must be one of %q or %q when mail driver is smtp", mailSMTPSecuritySSL, mailSMTPSecuritySTARTTLS)
		}
	default:
		return fmt.Errorf("unsupported mail driver %q", cfg.Mail.Driver)
	}

	switch cfg.MobileVerify.Driver {
	case "", mobileVerifyDriverNoop:
	case mobileVerifyDriverTwilio:
		if cfg.MobileVerify.TwilioAccountSID == "" {
			return fmt.Errorf("AGENTTEAM_MOBILE_VERIFY_TWILIO_ACCOUNT_SID is required when mobile verify driver is twilio")
		}
		if cfg.MobileVerify.TwilioAuthToken == "" {
			return fmt.Errorf("AGENTTEAM_MOBILE_VERIFY_TWILIO_AUTH_TOKEN is required when mobile verify driver is twilio")
		}
		hasFromNumber := cfg.MobileVerify.TwilioFromNumber != ""
		hasMessagingServiceSID := cfg.MobileVerify.TwilioMessagingServiceSID != ""
		if !hasFromNumber && !hasMessagingServiceSID {
			return fmt.Errorf("AGENTTEAM_MOBILE_VERIFY_TWILIO_FROM_NUMBER or AGENTTEAM_MOBILE_VERIFY_TWILIO_MESSAGING_SERVICE_SID is required when mobile verify driver is twilio")
		}
		if hasFromNumber && hasMessagingServiceSID {
			return fmt.Errorf("AGENTTEAM_MOBILE_VERIFY_TWILIO_FROM_NUMBER and AGENTTEAM_MOBILE_VERIFY_TWILIO_MESSAGING_SERVICE_SID are mutually exclusive")
		}
	default:
		return fmt.Errorf("unsupported mobile verify driver %q", cfg.MobileVerify.Driver)
	}

	if cfg.Auth.VerificationTokenTTL <= 0 {
		return fmt.Errorf("AGENTTEAM_AUTH_VERIFICATION_TOKEN_TTL must be positive")
	}
	if cfg.Auth.SessionTTL <= 0 {
		return fmt.Errorf("AGENTTEAM_AUTH_SESSION_TTL must be positive")
	}
	switch cfg.Auth.RegistrationModeOrDefault() {
	case registrationModeOpen, registrationModeInviteOnly:
	default:
		return fmt.Errorf("AGENTTEAM_AUTH_REGISTRATION_MODE must be one of %q or %q", registrationModeOpen, registrationModeInviteOnly)
	}
	for _, email := range cfg.Auth.RegistrationAllowlistEmails {
		if _, err := authidentity.NormalizeEmailAllowlistPattern(strings.TrimSpace(email)); err != nil {
			return fmt.Errorf("AGENTTEAM_AUTH_REGISTRATION_ALLOWLIST_EMAILS must contain valid email addresses or @domain patterns")
		}
	}
	if !cfg.Auth.PasswordAuthEnabled() {
		if !cfg.PingCAPAuth.Enabled {
			return fmt.Errorf("AGENTTEAM_PINGCAP_AUTH_ENABLED is required when AGENTTEAM_PASSWORD_AUTH_ENABLED=false")
		}
		if strings.TrimSpace(cfg.PingCAPAuth.Issuer) == "" {
			return fmt.Errorf("AGENTTEAM_PINGCAP_AUTH_ISSUER is required when PingCAP auth is the only login method")
		}
		if cfg.PingCAPAuth.webClientIDOrLegacy() == "" {
			return fmt.Errorf("AGENTTEAM_PINGCAP_AUTH_WEB_CLIENT_ID or AGENTTEAM_PINGCAP_AUTH_CLIENT_ID is required when PingCAP auth is the only login method")
		}
	}
	if cfg.Messages.HotRetentionDays <= 0 {
		return fmt.Errorf("AGENTTEAM_MESSAGES_RETENTION_DAYS must be positive")
	}
	if _, err := cfg.Feishu.CredentialEncryptionKey(); err != nil {
		return err
	}
	if _, err := cfg.AppCredentials.CredentialEncryptionKey(); err != nil {
		return err
	}
	if cfg.UnreadRuntime.SnapshotFlushInterval < 0 {
		return fmt.Errorf("AGENTTEAM_UNREAD_RUNTIME_SNAPSHOT_FLUSH_INTERVAL must be positive")
	}
	if cfg.UnreadRuntime.SnapshotFlushBatchSize < 0 {
		return fmt.Errorf("AGENTTEAM_UNREAD_RUNTIME_SNAPSHOT_FLUSH_BATCH_SIZE must be positive")
	}
	if cfg.UnreadRuntime.HydrateMaxConcurrency < 0 {
		return fmt.Errorf("AGENTTEAM_UNREAD_RUNTIME_HYDRATE_MAX_CONCURRENCY must be positive")
	}
	if cfg.UnreadRuntime.HydrateBatchSize < 0 {
		return fmt.Errorf("AGENTTEAM_UNREAD_RUNTIME_HYDRATE_BATCH_SIZE must be positive")
	}
	if cfg.MeshX.RequestTimeout < 0 {
		return fmt.Errorf("AGENTTEAM_MESHX_REQUEST_TIMEOUT must be non-negative")
	}
	if cfg.MeshX.ServiceTokenTTL < 0 {
		return fmt.Errorf("AGENTTEAM_MESHX_SERVICE_TOKEN_TTL must be non-negative")
	}
	if cfg.Billing.Alipay.RequestTimeout < 0 {
		return fmt.Errorf("AGENTTEAM_ALIPAY_REQUEST_TIMEOUT must be non-negative")
	}
	requireHTTPS := strings.EqualFold(cfg.Environment, "production")
	if err := cfg.Billing.Validate(cfg.LLM, requireHTTPS); err != nil {
		return err
	}
	if cfg.HTTP.ShutdownTimeout <= 0 {
		return fmt.Errorf("AGENTTEAM_HTTP_SHUTDOWN_TIMEOUT must be positive")
	}
	if cfg.HTTP.ReadyzDependencyTimeout < 0 {
		return fmt.Errorf("AGENTTEAM_READYZ_DEPENDENCY_TIMEOUT must be non-negative")
	}
	if cfg.HTTP.StartupSchemaTimeout < 0 {
		return fmt.Errorf("AGENTTEAM_STARTUP_SCHEMA_TIMEOUT must be non-negative")
	}
	if strings.TrimSpace(cfg.Observability.MetricsAddress) != "" && strings.TrimSpace(cfg.Observability.MetricsAddress) == strings.TrimSpace(cfg.HTTP.Address) {
		return fmt.Errorf("AGENTTEAM_OBSERVABILITY_METRICS_ADDRESS must not equal AGENTTEAM_HTTP_ADDRESS")
	}
	if err := validateObservabilityOTLPEndpoint(cfg.Observability.OTLPEndpoint); err != nil {
		return err
	}
	if err := validateObservabilityOTLPHeaders(cfg.Observability.OTLPHeaders, "AGENTTEAM_OBSERVABILITY_OTLP_HEADERS"); err != nil {
		return err
	}
	if cfg.Observability.TraceSampleRatio < 0 || cfg.Observability.TraceSampleRatio > 1 {
		return fmt.Errorf("AGENTTEAM_OBSERVABILITY_TRACE_SAMPLE_RATIO must be in [0, 1]")
	}
	if cfg.Database.ConnectTimeout <= 0 {
		return fmt.Errorf("AGENTTEAM_DATABASE_CONNECT_TIMEOUT must be positive")
	}
	if cfg.Database.Pool.MaxOpenConns < 0 {
		return fmt.Errorf("AGENTTEAM_DATABASE_MAX_OPEN_CONNS must be non-negative")
	}
	if cfg.Database.Pool.MaxIdleConns < 0 {
		return fmt.Errorf("AGENTTEAM_DATABASE_MAX_IDLE_CONNS must be non-negative")
	}
	if cfg.Database.Pool.MaxOpenConns > 0 && cfg.Database.Pool.MaxIdleConns > cfg.Database.Pool.MaxOpenConns {
		return fmt.Errorf("AGENTTEAM_DATABASE_MAX_IDLE_CONNS must be less than or equal to AGENTTEAM_DATABASE_MAX_OPEN_CONNS when AGENTTEAM_DATABASE_MAX_OPEN_CONNS is set")
	}
	if cfg.Database.Pool.ConnMaxLifetime < 0 {
		return fmt.Errorf("AGENTTEAM_DATABASE_CONN_MAX_LIFETIME must be non-negative")
	}
	if cfg.Database.Pool.ConnMaxIdleTime < 0 {
		return fmt.Errorf("AGENTTEAM_DATABASE_CONN_MAX_IDLE_TIME must be non-negative")
	}
	if _, err := cfg.LLM.OutboundProxyURL(); err != nil {
		return err
	}
	if err := cfg.LLM.ValidateMaaSDirect(requireHTTPS, cfg.Billing.Enabled); err != nil {
		return err
	}
	if cfg.Daemon.LatestVersionCacheTTL <= 0 {
		return fmt.Errorf("AGENTTEAM_DAEMON_LATEST_VERSION_CACHE_TTL must be positive")
	}
	if strings.TrimSpace(cfg.Daemon.LatestVersionOverride) != "" {
		if _, err := versioning.CompareSemver(cfg.Daemon.LatestVersionOverride, "0.0.0"); err != nil {
			return fmt.Errorf("AGENTTEAM_DAEMON_LATEST_VERSION_OVERRIDE must be valid semver: %w", err)
		}
	}
	if strings.TrimSpace(cfg.Daemon.MinDaemonVersion) != "" {
		if _, err := versioning.CompareSemver(cfg.Daemon.MinDaemonVersion, "0.0.0"); err != nil {
			return fmt.Errorf("AGENTTEAM_DAEMON_MIN_VERSION must be valid semver: %w", err)
		}
	}
	if err := validateBaseURL(cfg.PublicBaseURL, "AGENTTEAM_PUBLIC_BASE_URL", requireHTTPS); err != nil {
		return err
	}
	if err := validateBaseURL(cfg.Daemon.ConnectBaseURL, "AGENTTEAM_DAEMON_CONNECT_BASE_URL", requireHTTPS); err != nil {
		return err
	}
	if err := validateBaseURL(cfg.Daemon.LatestVersionURL, "AGENTTEAM_DAEMON_LATEST_VERSION_URL", requireHTTPS); err != nil {
		return err
	}
	if err := validateBaseURL(cfg.Billing.Alipay.GatewayURL, "AGENTTEAM_ALIPAY_GATEWAY_URL", requireHTTPS); err != nil {
		return err
	}
	if strings.TrimSpace(cfg.Billing.ReturnBaseURL) != "" {
		if err := validateAbsoluteBaseURL(cfg.Billing.ReturnBaseURL, "AGENTTEAM_BILLING_RETURN_BASE_URL", requireHTTPS); err != nil {
			return err
		}
	}
	return nil
}

func serverProfileFromEnv() (string, error) {
	value, ok := os.LookupEnv("AGENTTEAM_SERVER_PROFILE")
	if !ok {
		return ServerProfileAll, nil
	}
	profile := strings.ToLower(strings.TrimSpace(value))
	if profile == "" {
		return "", fmt.Errorf("AGENTTEAM_SERVER_PROFILE must be one of %q, %q, %q, or %q", ServerProfileAll, ServerProfileCentral, ServerProfileWorkspace, ServerProfileExtension)
	}
	return profile, nil
}

func (cfg ServerConfig) ProfileOrDefault() string {
	profile := strings.ToLower(strings.TrimSpace(cfg.Profile))
	if profile == "" {
		return ServerProfileAll
	}
	return profile
}

func (cfg ServerConfig) DefaultWorkspaceGroupIDOrDefault() string {
	groupID := strings.TrimSpace(cfg.DefaultWorkspaceGroupID)
	if groupID == "" {
		return DefaultWorkspaceGroupID
	}
	return groupID
}

func (cfg ServerConfig) Validate() error {
	switch cfg.ProfileOrDefault() {
	case ServerProfileAll, ServerProfileCentral, ServerProfileExtension:
		// no extra requirements
	case ServerProfileWorkspace:
		if strings.TrimSpace(cfg.WorkspaceGroupID) == "" {
			return fmt.Errorf("AGENTTEAM_WORKSPACE_GROUP_ID is required when AGENTTEAM_SERVER_PROFILE=workspace")
		}
	default:
		return fmt.Errorf("AGENTTEAM_SERVER_PROFILE must be one of %q, %q, %q, or %q", ServerProfileAll, ServerProfileCentral, ServerProfileWorkspace, ServerProfileExtension)
	}
	seenIdentity := map[string]struct{}{}
	seenSecret := map[string]struct{}{}
	for index, configured := range cfg.AppNotificationInternalServices {
		identityID := strings.TrimSpace(configured.IdentityID)
		secret := strings.TrimSpace(configured.Secret)
		definitionSlug := strings.TrimSpace(configured.ServiceDefinitionSlug)
		if identityID == "" || secret == "" || definitionSlug == "" || len(normalizedConfigStrings(configured.AllowedTargetKinds)) == 0 || len(normalizedConfigStrings(configured.AllowedContentKinds)) == 0 {
			return fmt.Errorf("App notification internal service entry %d requires identityId, secret, serviceDefinitionSlug, allowedTargetKinds, and allowedContentKinds", index)
		}
		if strings.IndexFunc(secret, func(value rune) bool { return unicode.IsSpace(value) || unicode.IsControl(value) }) >= 0 {
			return fmt.Errorf("App notification internal service entry %d secret must be a single HTTP token", index)
		}
		if _, duplicate := seenIdentity[identityID]; duplicate {
			return fmt.Errorf("App notification internal services contain duplicate identityId %q", identityID)
		}
		if _, duplicate := seenSecret[secret]; duplicate {
			return fmt.Errorf("App notification internal services contain a duplicate service secret")
		}
		seenIdentity[identityID] = struct{}{}
		seenSecret[secret] = struct{}{}
	}
	return nil
}

func (cfg LLMConfig) OutboundProxyURL() (*url.URL, error) {
	return parseOutboundProxyURL(cfg.OutboundProxy, "AGENTTEAM_LLM_OUTBOUND_PROXY")
}

func (cfg LLMConfig) MaaSDirectConfigured() bool {
	return strings.TrimSpace(cfg.MaaSBaseURL) != "" &&
		strings.TrimSpace(cfg.MaaSRuntimeBaseURL) != "" &&
		strings.TrimSpace(cfg.MaaSInternalBearerToken) != "" &&
		strings.TrimSpace(cfg.MaaSCapabilityPrivateKeyFile) != ""
}

func (cfg LLMConfig) ValidateMaaSDirect(requireHTTPS bool, billingEnabled bool) error {
	if !cfg.maasAnyConfigured() {
		return nil
	}
	if billingEnabled && !cfg.maasRuntimeConfigured() {
		return nil
	}
	if strings.TrimSpace(cfg.MaaSBaseURL) == "" {
		return fmt.Errorf("AGENTTEAM_MAAS_BASE_URL is required when MaaS direct LLM is configured")
	}
	if strings.TrimSpace(cfg.MaaSRuntimeBaseURL) == "" {
		return fmt.Errorf("AGENTTEAM_MAAS_RUNTIME_BASE_URL is required when MaaS direct LLM is configured")
	}
	if strings.TrimSpace(cfg.MaaSInternalBearerToken) == "" {
		return fmt.Errorf("AGENTTEAM_MAAS_INTERNAL_BEARER_TOKEN is required when MaaS direct LLM is configured")
	}
	if strings.TrimSpace(cfg.MaaSCapabilityPrivateKeyFile) == "" {
		return fmt.Errorf("AGENTTEAM_MAAS_CAPABILITY_ED25519_PRIVATE_KEY_FILE is required when MaaS direct LLM is configured")
	}
	if err := validateMaaSServiceBaseURL(cfg.MaaSBaseURL, "AGENTTEAM_MAAS_BASE_URL", requireHTTPS); err != nil {
		return err
	}
	if err := validateMaaSRuntimeBaseURL(cfg.MaaSRuntimeBaseURL, requireHTTPS); err != nil {
		return err
	}
	parsedRuntimeBaseURL, _ := url.Parse(strings.TrimSpace(cfg.MaaSRuntimeBaseURL))
	if !strings.HasSuffix(strings.TrimRight(parsedRuntimeBaseURL.Path, "/"), "/maas/v1") {
		return fmt.Errorf("AGENTTEAM_MAAS_RUNTIME_BASE_URL path must end with /maas/v1")
	}
	return nil
}

func validateMaaSRuntimeBaseURL(raw string, requireHTTPS bool) error {
	const envName = "AGENTTEAM_MAAS_RUNTIME_BASE_URL"
	if err := validateMaaSServiceBaseURL(raw, envName, requireHTTPS); err != nil {
		return err
	}
	parsedBaseURL, err := url.Parse(strings.TrimSpace(raw))
	if err != nil {
		return fmt.Errorf("%s must be a valid URL: %w", envName, err)
	}
	if strings.EqualFold(parsedBaseURL.Scheme, "http") && !isLoopbackHost(parsedBaseURL.Hostname()) {
		return fmt.Errorf("%s must use https unless it targets loopback", envName)
	}
	return nil
}

func isLoopbackHost(host string) bool {
	trimmed := strings.TrimSpace(host)
	if trimmed == "" {
		return false
	}
	if strings.EqualFold(trimmed, "localhost") {
		return true
	}
	ip := net.ParseIP(trimmed)
	return ip != nil && ip.IsLoopback()
}

func (cfg LLMConfig) maasAnyConfigured() bool {
	return strings.TrimSpace(cfg.MaaSBaseURL) != "" ||
		strings.TrimSpace(cfg.MaaSRuntimeBaseURL) != "" ||
		strings.TrimSpace(cfg.MaaSInternalBearerToken) != "" ||
		strings.TrimSpace(cfg.MaaSCapabilityPrivateKeyFile) != ""
}

func (cfg LLMConfig) maasRuntimeConfigured() bool {
	return strings.TrimSpace(cfg.MaaSRuntimeBaseURL) != "" ||
		strings.TrimSpace(cfg.MaaSCapabilityPrivateKeyFile) != ""
}

func (cfg BillingConfig) Validate(llm LLMConfig, requireHTTPS bool) error {
	if !cfg.Enabled {
		return nil
	}
	if strings.TrimSpace(cfg.Alipay.AppID) == "" {
		return fmt.Errorf("AGENTTEAM_ALIPAY_APP_ID is required when AGENTTEAM_BILLING_ENABLED=true")
	}
	if strings.TrimSpace(cfg.Alipay.AppPrivateKey) == "" {
		return fmt.Errorf("AGENTTEAM_ALIPAY_APP_PRIVATE_KEY is required when AGENTTEAM_BILLING_ENABLED=true")
	}
	if strings.TrimSpace(cfg.Alipay.AlipayPublicKey) == "" {
		return fmt.Errorf("AGENTTEAM_ALIPAY_PUBLIC_KEY is required when AGENTTEAM_BILLING_ENABLED=true")
	}
	if strings.TrimSpace(cfg.Alipay.GatewayURL) == "" {
		return fmt.Errorf("AGENTTEAM_ALIPAY_GATEWAY_URL is required when AGENTTEAM_BILLING_ENABLED=true")
	}
	if err := validateBaseURL(cfg.Alipay.GatewayURL, "AGENTTEAM_ALIPAY_GATEWAY_URL", requireHTTPS); err != nil {
		return err
	}
	if strings.TrimSpace(llm.MaaSBaseURL) == "" {
		return fmt.Errorf("AGENTTEAM_MAAS_BASE_URL is required when AGENTTEAM_BILLING_ENABLED=true")
	}
	if strings.TrimSpace(llm.MaaSInternalBearerToken) == "" {
		return fmt.Errorf("AGENTTEAM_MAAS_INTERNAL_BEARER_TOKEN is required when AGENTTEAM_BILLING_ENABLED=true")
	}
	if err := validateMaaSServiceBaseURL(llm.MaaSBaseURL, "AGENTTEAM_MAAS_BASE_URL", requireHTTPS); err != nil {
		return err
	}
	return nil
}

func (cfg AuthConfig) RegistrationModeOrDefault() string {
	mode := strings.ToLower(strings.TrimSpace(cfg.RegistrationMode))
	if mode == "" {
		return registrationModeInviteOnly
	}
	return mode
}

func (cfg AuthConfig) PasswordAuthEnabled() bool {
	return !cfg.PasswordAuthDisabled
}

func firstNonEmptyEnv(keys ...string) string {
	for _, key := range keys {
		if value := strings.TrimSpace(os.Getenv(key)); value != "" {
			return value
		}
	}
	return ""
}

func firstNonEmptyEnvOrDefault(defaultValue string, keys ...string) string {
	if value := firstNonEmptyEnv(keys...); value != "" {
		return value
	}
	return defaultValue
}

func coreStorageDSNFromEnv() string {
	return envcompat.String("AGENTTEAM_DATABASE_DSN", "", "AGENTTEAM_CORE_STORAGE_DSN")
}

func (cfg DaemonConfig) ConnectBaseURLOrDefault(publicBaseURL string) string {
	if trimmed := strings.TrimSpace(cfg.ConnectBaseURL); trimmed != "" {
		return trimmed
	}
	return strings.TrimSpace(publicBaseURL)
}
func (cfg FeishuConfig) CredentialEncryptionKey() ([]byte, error) {
	encodedKey := strings.TrimSpace(cfg.OrgCredentialsMasterKey)
	if encodedKey == "" {
		return nil, nil
	}
	return decodeCredentialEncryptionKey(encodedKey, "AGENTTEAM_ORG_CREDENTIALS_MASTER_KEY")
}

type CredentialKeyResolution struct {
	Key    []byte
	Source string
	Path   string
}

type FeishuCredentialKeyResolution = CredentialKeyResolution

func (cfg FeishuConfig) ResolveCredentialEncryptionKey() (FeishuCredentialKeyResolution, error) {
	if key, err := cfg.CredentialEncryptionKey(); err != nil {
		return FeishuCredentialKeyResolution{}, err
	} else if len(key) > 0 {
		return FeishuCredentialKeyResolution{
			Key:    key,
			Source: "environment",
		}, nil
	}

	keyFilePath := strings.TrimSpace(cfg.OrgCredentialsKeyFile)
	if keyFilePath == "" {
		return FeishuCredentialKeyResolution{}, nil
	}

	key, generated, err := loadOrGenerateCredentialEncryptionKeyFile(keyFilePath, "AGENTTEAM_ORG_CREDENTIALS_MASTER_KEY", "org credential")
	if err != nil {
		return FeishuCredentialKeyResolution{}, err
	}
	source := "file"
	if generated {
		source = "generated"
	}
	return FeishuCredentialKeyResolution{
		Key:    key,
		Source: source,
		Path:   keyFilePath,
	}, nil
}

func (cfg AppCredentialsConfig) CredentialEncryptionKey() ([]byte, error) {
	encodedKey := strings.TrimSpace(cfg.MasterKey)
	if encodedKey == "" {
		return nil, nil
	}
	return decodeCredentialEncryptionKey(encodedKey, "AGENTTEAM_APP_CREDENTIALS_MASTER_KEY")
}

type AppCredentialKeyResolution = CredentialKeyResolution

func (cfg AppCredentialsConfig) ResolveCredentialEncryptionKey() (AppCredentialKeyResolution, error) {
	if key, err := cfg.CredentialEncryptionKey(); err != nil {
		return AppCredentialKeyResolution{}, err
	} else if len(key) > 0 {
		return AppCredentialKeyResolution{
			Key:    key,
			Source: "environment",
		}, nil
	}

	keyFilePath := strings.TrimSpace(cfg.KeyFile)
	if keyFilePath == "" {
		return AppCredentialKeyResolution{}, nil
	}

	key, generated, err := loadOrGenerateCredentialEncryptionKeyFile(keyFilePath, "AGENTTEAM_APP_CREDENTIALS_MASTER_KEY", "app credential")
	if err != nil {
		return AppCredentialKeyResolution{}, err
	}
	source := "file"
	if generated {
		source = "generated"
	}
	return AppCredentialKeyResolution{
		Key:    key,
		Source: source,
		Path:   keyFilePath,
	}, nil
}

func loadOrGenerateCredentialEncryptionKeyFile(path string, keyEnvName string, label string) ([]byte, bool, error) {
	key, err := readCredentialEncryptionKeyFile(path, keyEnvName, label)
	if err == nil {
		return key, false, nil
	}
	if !errors.Is(err, os.ErrNotExist) {
		return nil, false, err
	}

	generatedKey := make([]byte, 32)
	if _, err := rand.Read(generatedKey); err != nil {
		return nil, false, fmt.Errorf("generate %s master key: %w", label, err)
	}

	if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
		return nil, false, fmt.Errorf("create %s key directory: %w", label, err)
	}

	file, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600)
	if err != nil {
		if errors.Is(err, os.ErrExist) {
			key, reloadErr := readCredentialEncryptionKeyFile(path, keyEnvName, label)
			return key, false, reloadErr
		}
		return nil, false, fmt.Errorf("create %s key file: %w", label, err)
	}
	defer file.Close()

	encodedKey := base64.StdEncoding.EncodeToString(generatedKey)
	if _, err := file.WriteString(encodedKey); err != nil {
		return nil, false, fmt.Errorf("write %s key file: %w", label, err)
	}
	if err := file.Sync(); err != nil {
		return nil, false, fmt.Errorf("sync %s key file: %w", label, err)
	}
	return generatedKey, true, nil
}

func validateBaseURL(raw string, envName string, requireHTTPS bool) error {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return nil
	}

	parsedBaseURL, err := url.Parse(trimmed)
	if err != nil {
		return fmt.Errorf("%s must be a valid URL: %w", envName, err)
	}

	switch strings.ToLower(parsedBaseURL.Scheme) {
	case "http":
		if requireHTTPS {
			return fmt.Errorf("%s must use https in production", envName)
		}
	case "https":
	default:
		return fmt.Errorf("%s must use http(s)", envName)
	}

	return nil
}

func validateMaaSServiceBaseURL(raw string, envName string, requireHTTPS bool) error {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return nil
	}

	parsedBaseURL, err := url.Parse(trimmed)
	if err != nil {
		return fmt.Errorf("%s must be a valid URL: %w", envName, err)
	}
	if requireHTTPS && strings.EqualFold(parsedBaseURL.Scheme, "http") && isMaaSInternalHTTPHost(parsedBaseURL.Hostname()) {
		return validateAbsoluteBaseURL(raw, envName, false)
	}
	if err := validateAbsoluteBaseURL(raw, envName, requireHTTPS); err != nil {
		return err
	}
	return nil
}

func isMaaSInternalHTTPHost(host string) bool {
	normalized := strings.Trim(strings.ToLower(strings.TrimSpace(host)), ".")
	if normalized == "" {
		return false
	}
	if isLoopbackHost(normalized) || normalized == "agentteam-maas" {
		return true
	}

	labels := strings.Split(normalized, ".")
	if len(labels) == 3 && labels[0] == "agentteam-maas" && labels[2] == "svc" {
		return labels[1] != ""
	}
	if len(labels) == 5 && labels[0] == "agentteam-maas" && labels[2] == "svc" && labels[3] == "cluster" && labels[4] == "local" {
		return labels[1] != ""
	}
	return false
}

func validateAbsoluteBaseURL(raw string, envName string, requireHTTPS bool) error {
	if err := validateBaseURL(raw, envName, requireHTTPS); err != nil {
		return err
	}
	parsedBaseURL, err := url.Parse(strings.TrimSpace(raw))
	if err != nil {
		return fmt.Errorf("%s must be a valid URL: %w", envName, err)
	}
	if strings.TrimSpace(parsedBaseURL.Host) == "" {
		return fmt.Errorf("%s must include a host", envName)
	}
	if parsedBaseURL.User != nil {
		return fmt.Errorf("%s must not include user info", envName)
	}
	if parsedBaseURL.RawQuery != "" {
		return fmt.Errorf("%s must not include a query string", envName)
	}
	if parsedBaseURL.Fragment != "" {
		return fmt.Errorf("%s must not include a fragment", envName)
	}
	return nil
}

func validateObservabilityOTLPEndpoint(raw string) error {
	const envName = "AGENTTEAM_OBSERVABILITY_OTLP_ENDPOINT"
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return nil
	}

	parsedEndpoint, err := url.Parse(trimmed)
	if err != nil {
		return fmt.Errorf("%s must be a valid URL: %w", envName, err)
	}
	switch strings.ToLower(parsedEndpoint.Scheme) {
	case "http", "https":
	default:
		return fmt.Errorf("%s must use http(s)", envName)
	}
	if strings.TrimSpace(parsedEndpoint.Host) == "" {
		return fmt.Errorf("%s must include a host", envName)
	}
	if parsedEndpoint.User != nil {
		return fmt.Errorf("%s must not include user info", envName)
	}
	if parsedEndpoint.RawQuery != "" {
		return fmt.Errorf("%s must not include a query string", envName)
	}
	if parsedEndpoint.Fragment != "" {
		return fmt.Errorf("%s must not include a fragment", envName)
	}
	return nil
}

func parseOutboundProxyURL(raw string, envName string) (*url.URL, error) {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return nil, nil
	}
	if !strings.Contains(trimmed, "://") {
		return &url.URL{
			Scheme: "socks5",
			Host:   trimmed,
		}, nil
	}

	parsed, err := url.Parse(trimmed)
	if err != nil {
		return nil, fmt.Errorf("%s must be a valid proxy URL: %w", envName, err)
	}
	if strings.TrimSpace(parsed.Host) == "" {
		return nil, fmt.Errorf("%s must include a proxy host", envName)
	}
	switch strings.ToLower(strings.TrimSpace(parsed.Scheme)) {
	case "http", "https", "socks5", "socks5h":
	default:
		return nil, fmt.Errorf("%s must use http, https, socks5, or socks5h", envName)
	}
	return parsed, nil
}

func readCredentialEncryptionKeyFile(path string, keyEnvName string, label string) ([]byte, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}

	key, err := decodeCredentialEncryptionKey(strings.TrimSpace(string(data)), keyEnvName)
	if err != nil {
		return nil, fmt.Errorf("load %s key file %q: %w", label, path, err)
	}
	return key, nil
}

func decodeCredentialEncryptionKey(encodedKey string, envName string) ([]byte, error) {
	key, err := base64.StdEncoding.DecodeString(encodedKey)
	if err != nil {
		key, err = base64.RawStdEncoding.DecodeString(encodedKey)
	}
	if err != nil {
		return nil, fmt.Errorf("%s must be base64-encoded 32-byte key", envName)
	}
	if len(key) != 32 {
		return nil, fmt.Errorf("%s must decode to exactly 32 bytes", envName)
	}
	return key, nil
}

// EmailVerificationRequired reports whether new registrations must verify email before login.
func (cfg Config) EmailVerificationRequired() bool {
	return cfg.Mail.Driver == mailDriverSES || cfg.Mail.Driver == mailDriverSMTP
}

func validatePlainMailAddress(value string, driver string) error {
	if value == "" {
		return fmt.Errorf("AGENTTEAM_MAIL_FROM_ADDRESS is required when mail driver is %s", driver)
	}
	return validatePlainEmailAddress(value, "AGENTTEAM_MAIL_FROM_ADDRESS")
}

func validatePlainEmailAddress(value string, field string) error {
	parsedFrom, err := netmail.ParseAddress(value)
	if err != nil {
		return fmt.Errorf("%s must be a valid email address: %w", field, err)
	}
	if parsedFrom.Address != value {
		return fmt.Errorf("%s must be a plain email address (e.g. noreply@example.com); got %q", field, value)
	}
	return nil
}

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

func envOrDefaultAllowExplicitEmpty(key string, fallback string) string {
	value, ok := os.LookupEnv(key)
	if !ok {
		return fallback
	}
	return strings.TrimSpace(value)
}

func boolEnvOrDefault(key string, fallback bool) (bool, error) {
	value := strings.ToLower(strings.TrimSpace(os.Getenv(key)))
	switch value {
	case "":
		return fallback, nil
	case "true":
		return true, nil
	case "false":
		return false, nil
	default:
		return false, fmt.Errorf("%s must be true or false", key)
	}
}

func csvEnv(key string) []string {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return nil
	}
	parts := strings.Split(value, ",")
	items := make([]string, 0, len(parts))
	for _, part := range parts {
		item := strings.TrimSpace(part)
		if item != "" {
			items = append(items, item)
		}
	}
	return items
}

func otlpHeadersEnv(key string) (map[string]string, error) {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return nil, nil
	}
	headers := make(map[string]string)
	headerKeys := make(map[string]string)
	for _, part := range strings.Split(value, ",") {
		item := strings.TrimSpace(part)
		if item == "" {
			continue
		}
		headerKey, headerValue, ok := strings.Cut(item, "=")
		if !ok {
			return nil, fmt.Errorf("%s must use key=value pairs separated by commas", key)
		}
		headerKey = strings.TrimSpace(headerKey)
		headerValue = strings.TrimSpace(headerValue)
		if err := validateObservabilityOTLPHeader(headerKey, headerValue, key); err != nil {
			return nil, err
		}
		lookupKey := strings.ToLower(headerKey)
		if duplicateKey, exists := headerKeys[lookupKey]; exists {
			return nil, fmt.Errorf("%s contains duplicate header %q", key, duplicateKey)
		}
		headerKeys[lookupKey] = headerKey
		headers[headerKey] = headerValue
	}
	if len(headers) == 0 {
		return nil, nil
	}
	return headers, nil
}

func validateObservabilityOTLPHeaders(headers map[string]string, envName string) error {
	headerKeys := make(map[string]string, len(headers))
	for key, value := range headers {
		if err := validateObservabilityOTLPHeader(key, value, envName); err != nil {
			return err
		}
		lookupKey := strings.ToLower(key)
		if duplicateKey, exists := headerKeys[lookupKey]; exists {
			return fmt.Errorf("%s contains duplicate header %q", envName, duplicateKey)
		}
		headerKeys[lookupKey] = key
	}
	return nil
}

func validateObservabilityOTLPHeader(key string, value string, envName string) error {
	if !validHTTPHeaderName(key) {
		return fmt.Errorf("%s contains an invalid header name", envName)
	}
	if value == "" || strings.ContainsAny(value, "\r\n") {
		return fmt.Errorf("%s contains an invalid header value", envName)
	}
	return nil
}

func validHTTPHeaderName(name string) bool {
	if name == "" {
		return false
	}
	for _, ch := range name {
		if ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9' {
			continue
		}
		switch ch {
		case '!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~':
			continue
		default:
			return false
		}
	}
	return true
}

func durationOrDefault(key string, fallback time.Duration) (time.Duration, error) {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return fallback, nil
	}
	duration, err := time.ParseDuration(value)
	if err != nil {
		return 0, fmt.Errorf("parse %s: %w", key, err)
	}
	return duration, nil
}

func optionalIntEnv(key string) (int, bool, error) {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return 0, false, nil
	}
	parsed, err := strconv.Atoi(value)
	if err != nil {
		return 0, false, fmt.Errorf("parse %s: %w", key, err)
	}
	return parsed, true, nil
}

func intOrDefault(key string, fallback int) (int, error) {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return fallback, nil
	}
	parsed, err := strconv.Atoi(value)
	if err != nil {
		return 0, fmt.Errorf("parse %s: %w", key, err)
	}
	return parsed, nil
}

func floatOrDefault(key string, fallback float64) (float64, error) {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return fallback, nil
	}
	parsed, err := strconv.ParseFloat(value, 64)
	if err != nil {
		return 0, fmt.Errorf("parse %s: %w", key, err)
	}
	return parsed, nil
}

func defaultTraceSampleRatio(environment string) float64 {
	if strings.EqualFold(strings.TrimSpace(environment), "production") {
		return 0.05
	}
	return 1.0
}

func resolvedDatabaseMaxOpenConns(value int, set bool) int {
	if !set {
		return 0
	}
	return value
}

func resolvedDatabaseMaxIdleConns(maxOpen int, maxOpenSet bool, maxIdle int, maxIdleSet bool) int {
	if maxIdleSet {
		return maxIdle
	}
	if maxOpenSet && maxOpen > 0 {
		return maxOpen
	}
	return 2
}
