// Package runtimeconfig owns the deployment-facing environment contract for
// independently packaged Runtime roles. It does not authenticate workload
// identity; P6 owns that authority.
package runtimeconfig

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	RuntimeAPIAdminAddressEnv                  = "NEXUSCRM_RUNTIME_API_ADMIN_ADDR"
	RuntimeAPIGRPCAddressEnv                   = "NEXUSCRM_RUNTIME_API_GRPC_ADDR"
	RuntimeAPIAdmissionLimitEnv                = "NEXUSCRM_RUNTIME_API_ADMISSION_LIMIT"
	RuntimeAPIProfilingEnabledEnv              = "NEXUSCRM_RUNTIME_API_PROFILING_ENABLED"
	RuntimeAPISQLDiagnosticsEnabledEnv         = "NEXUSCRM_RUNTIME_API_SQL_DIAGNOSTICS_ENABLED"
	RuntimeAPIServingConfigFileEnv             = "NEXUSCRM_RUNTIME_API_SERVING_CONFIG_FILE"
	RuntimeAPISigningKeyFileEnv                = "NEXUSCRM_RUNTIME_API_SIGNING_KEY_FILE"
	RuntimeAPITLSCertificateFileEnv            = "NEXUSCRM_RUNTIME_API_TLS_CERTIFICATE_FILE"
	RuntimeAPITLSPrivateKeyFileEnv             = "NEXUSCRM_RUNTIME_API_TLS_PRIVATE_KEY_FILE"
	RuntimeWorkerAdminAddressEnv               = "NEXUSCRM_RUNTIME_WORKER_ADMIN_ADDR"
	RuntimeWorkerConfigFileEnv                 = "NEXUSCRM_RUNTIME_WORKER_CONFIG_FILE"
	RuntimeWorkerCatalogReconcileConfigFileEnv = "NEXUSCRM_RUNTIME_WORKER_CATALOG_RECONCILE_CONFIG_FILE"
	RuntimeWorkerTimedFlowOwnerConfigFileEnv   = "NEXUSCRM_RUNTIME_WORKER_TIMED_FLOW_OWNER_CONFIG_FILE"
	WorkloadIdentityTokenFileEnv               = "NEXUSCRM_RUNTIME_WORKLOAD_IDENTITY_TOKEN_FILE"
	DefaultRuntimeAPIAdminAddress              = ":9091"
	DefaultRuntimeAPIGRPCAddress               = ":9090"
	DefaultWorkerAdminAddress                  = ":9191"
	DefaultAPIAdmissionLimit                   = 16
	MaxAPIAdmissionLimit                       = 64
	MaxWorkerBatchSize                         = 100
	MaxWorkerParallelism                       = 15
	MaxWorkerAttempts                          = 20
	MaxWorkerDBConnections                     = 32
	MaxWorkerPollInterval                      = time.Minute
)

type LookupEnv func(string) (string, bool)

type API struct {
	AdminAddress              string
	GRPCAddress               string
	AdmissionLimit            int
	ProfilingEnabled          bool
	SQLDiagnosticsEnabled     bool
	WorkloadIdentityTokenFile string
	SigningKeyFile            string
	TLSCertificateFile        string
	TLSPrivateKeyFile         string
	Serving                   Serving
}

const (
	ServingConfigSchemaV2     = "nexuscrm.runtime-api-serving/v2"
	ServingConfigSchemaV3     = "nexuscrm.runtime-api-serving/v3"
	ServingConfigSchemaV4     = "nexuscrm.runtime-api-serving/v4"
	PermissionStateProtocolV1 = "permission_state.v1"
)

// ServingCapability binds one Catalog-owned L1 handler capability to one L0
// Runtime capability. Its receipt and epoch attest the initial deployment;
// Runtime verifies that anchor and admits the current Catalog publication of
// the same App/service release and workload. They are not mutable version pins.
type ServingCapability struct {
	AppCapabilityID                    string `json:"app_capability_id"`
	RuntimeCapabilityID                string `json:"runtime_capability_id"`
	AuthorizedWorkloadID               string `json:"authorized_workload_id,omitempty"`
	ExpectedOwnershipEpoch             uint64 `json:"expected_ownership_epoch"`
	ExpectedCompatibilityReceiptDigest string `json:"expected_compatibility_receipt_digest"`
	AdmissionLease                     string `json:"admission_lease"`
	// OutcomeRetention is present only for an L0 capability whose semantic
	// contract owns durable command outcomes. Runtime production, not this
	// generic parser, decides which capability may require it.
	OutcomeRetention string `json:"outcome_retention,omitempty"`
}

// CatalogConsumerCapability binds one closed Catalog-derived capability to its
// exact workload consumer. Disclosure RPCs need no artifact selector. A
// definition-selecting consumer additionally attests the deployment snapshot.
// Runtime derives its current projection from the same active source snapshot
// as the complete prepared serving set.
type CatalogConsumerCapability struct {
	RuntimeCapabilityID   string `json:"runtime_capability_id"`
	AuthorizedWorkloadID  string `json:"authorized_workload_id"`
	CatalogSnapshotDigest string `json:"catalog_snapshot_digest,omitempty"`
	AdmissionLease        string `json:"admission_lease"`
}

// WorkloadCapability selects one Runtime-owned workload-consumer mechanism
// that has no Catalog metadata projection. The immutable serving artifact
// narrows the authenticated workload; the engine must still verify its durable
// AppInstallation and SecurityEpoch authority before performing any work.
type WorkloadCapability struct {
	ApplicationOwnerID   string `json:"application_owner_id,omitempty"`
	RuntimeCapabilityID  string `json:"runtime_capability_id"`
	AuthorizedWorkloadID string `json:"authorized_workload_id"`
	AdmissionLease       string `json:"admission_lease,omitempty"`
	// Mailbox authorization selects exactly one namespace; legacy custom-only
	// artifacts retain their semantics. Other workload capabilities reject both.
	RequiredCustomPermission string `json:"required_custom_permission,omitempty"`
	RequiredSystemPermission string `json:"required_system_permission,omitempty"`
}

// FederatedOIDCProvider is one operator-selected trust bundle for the neutral
// Runtime session capability. Provider identities and trust endpoints are
// deployment data; the Runtime binary does not predefine an L1 login vendor.
type FederatedOIDCProvider struct {
	ProviderID     string   `json:"provider_id"`
	Audience       string   `json:"audience"`
	SessionScope   string   `json:"session_scope"`
	TrustedIssuers []string `json:"trusted_issuers"`
	JWKSURL        string   `json:"jwks_url"`
}

type Serving struct {
	// UserAPICallers are authenticated, non-handler clients of delegated user APIs.
	UserAPICallers               []string                    `json:"user_api_callers,omitempty"`
	Schema                       string                      `json:"schema"`
	PermissionStateProtocol      string                      `json:"permission_state_protocol,omitempty"`
	ReplicaID                    string                      `json:"replica_id"`
	AuthorizedWorkloadID         string                      `json:"authorized_workload_id,omitempty"`
	AuthorizedWorkloadIDs        []string                    `json:"authorized_workload_ids,omitempty"`
	OrgID                        string                      `json:"org_id"`
	AppInstallationID            string                      `json:"app_installation_id"`
	CatalogSnapshotStoreRoot     string                      `json:"catalog_snapshot_store_root"`
	ExpectedServiceReleaseDigest string                      `json:"expected_service_release_digest"`
	SchemaGeneration             uint64                      `json:"schema_generation"`
	EngineGeneration             uint64                      `json:"engine_generation"`
	IdentityAttemptCap           string                      `json:"identity_attempt_cap"`
	SessionScopeCapabilities     map[string][]string         `json:"session_scope_capabilities"`
	Capabilities                 []ServingCapability         `json:"capabilities"`
	CatalogConsumerCapabilities  []CatalogConsumerCapability `json:"catalog_consumer_capabilities,omitempty"`
	WorkloadCapabilities         []WorkloadCapability        `json:"workload_capabilities,omitempty"`
	FederatedOIDCProviders       []FederatedOIDCProvider     `json:"federated_oidc_providers,omitempty"`
}

type Worker struct {
	AdminAddress              string
	WorkloadIdentityTokenFile string
	// CatalogReconcileConfigFile is the immutable deployment-owned serving
	// selection used only by a publication-capable Platform-command worker.
	// Runtime parses it once at startup and never derives the selection from a
	// Catalog revision or mutable active pointer.
	CatalogReconcileConfigFile string
	Role                       WorkerRole
	// TimedFlowOwner is an optional exact scheduled-Flow role assertion used
	// only by the record-event composition. It reuses the deployment-owned
	// WorkerRole contract instead of introducing a second handoff authority.
	TimedFlowOwner *WorkerRole
}

const (
	ServerPermissionStateLegacySchema   = "nexuscrm.server-permission-state/legacy"
	ServerPermissionStateConfigSchemaV1 = "nexuscrm.server-permission-state/v1"
)

const (
	WorkerConfigSchemaV1 = "nexuscrm.runtime-worker-role/v1"
	WorkerConfigSchemaV2 = "nexuscrm.runtime-worker-role/v2"
	WorkerConfigSchemaV3 = "nexuscrm.runtime-worker-role/v3"

	DefaultWorkerOwnershipHealthInterval = 30 * time.Second
)

// WorkerRole is the deployment-owned configuration for one Runtime work
// family. Schema v1 covers payload-only work; schema v2 adds the immutable
// metadata scope required by metadata-interpreting work. Schema v3 preserves
// both scope shapes and adds the Permission State v1 rollout fence. The
// database authority remains _System_RuntimeWorkOwnership; this document only
// names the exact authority tuple the process must verify and lock before
// effects.
type WorkerRole struct {
	Schema                  string `json:"schema"`
	PermissionStateProtocol string `json:"permission_state_protocol,omitempty"`
	WorkFamily              string `json:"work_family"`
	DeploymentID            string `json:"deployment_id"`
	WorkerReleaseDigest     string `json:"worker_release_digest"`
	WorkerSourceRevision    string `json:"worker_source_revision"`
	OwnershipEpoch          uint64 `json:"ownership_epoch"`
	BatchSize               int    `json:"batch_size"`
	// Parallelism is optional for existing role documents and defaults to one.
	// Independently keyed workers may opt into bounded long-lived lanes; the
	// validation below reserves pool capacity for each effect and heartbeat.
	Parallelism  int    `json:"parallelism,omitempty"`
	PollInterval string `json:"poll_interval"`
	// OwnershipHealthInterval is the process-level authority check cadence.
	// Durable work remains fenced transactionally; this independent cadence
	// only bounds how long an otherwise idle process can remain Ready after an
	// ownership transfer. Existing role documents use the safe default.
	OwnershipHealthInterval string `json:"ownership_health_interval,omitempty"`
	LeaseDuration           string `json:"lease_duration"`
	RetryJitterMax          string `json:"retry_jitter_max"`
	// ProcessedRetention is optional grammar. The work-family owner decides
	// whether it is required or forbidden; runtimeconfig only proves that a
	// declared retention is a positive, non-overflowing duration.
	ProcessedRetention       string `json:"processed_retention,omitempty"`
	ProcessedCleanupInterval string `json:"processed_cleanup_interval,omitempty"`
	MaxAttempts              int    `json:"max_attempts"`
	DBMaxOpenConns           int    `json:"db_max_open_conns"`
	DBMaxIdleConns           int    `json:"db_max_idle_conns"`
	ShutdownTimeout          string `json:"shutdown_timeout"`
	// v2/v3 immutable metadata execution scope. Durable-row roles reopen the
	// exact identity carried by each row. The scheduled discovery role also
	// supplies the opaque App capability and exact Catalog-published serving
	// receipt selected by the stopped-upgrade candidate; it never reads an
	// active/latest Catalog pointer inside the Worker.
	OrgID                              string `json:"org_id,omitempty"`
	AppInstallationID                  string `json:"app_installation_id,omitempty"`
	AppCapabilityID                    string `json:"app_capability_id,omitempty"`
	RuntimeCapabilityID                string `json:"runtime_capability_id,omitempty"`
	ExpectedCompatibilityReceiptDigest string `json:"expected_compatibility_receipt_digest,omitempty"`
	CatalogSnapshotStoreRoot           string `json:"catalog_snapshot_store_root,omitempty"`
	SchemaGeneration                   uint64 `json:"schema_generation,omitempty"`
	EngineGeneration                   uint64 `json:"engine_generation,omitempty"`
}

// WorkerMetadataScope is the deployment-owned routing and compatibility
// projection required by work that interprets immutable Catalog content.
type WorkerMetadataScope struct {
	OrgID                              string
	AppInstallationID                  string
	AppCapabilityID                    string
	RuntimeCapabilityID                string
	ExpectedCompatibilityReceiptDigest string
	CatalogSnapshotStoreRoot           string
	SchemaGeneration                   uint64
	EngineGeneration                   uint64
}

func LoadAPI(lookup LookupEnv) (API, error) {
	config, err := loadAPITransport(lookup)
	if err != nil {
		return API{}, err
	}
	lookup = normalizeLookup(lookup)
	config.WorkloadIdentityTokenFile = valueOr(lookup, WorkloadIdentityTokenFileEnv, "")
	config.SigningKeyFile = valueOr(lookup, RuntimeAPISigningKeyFileEnv, "")
	servingConfigFile := valueOr(lookup, RuntimeAPIServingConfigFileEnv, "")
	if err := validateIdentityTokenFile(config.WorkloadIdentityTokenFile); err != nil {
		return API{}, err
	}
	if err := validateOwnerFile(RuntimeAPISigningKeyFileEnv, config.SigningKeyFile); err != nil {
		return API{}, err
	}
	if err := validateRegularFile(RuntimeAPIServingConfigFileEnv, servingConfigFile); err != nil {
		return API{}, err
	}
	config.Serving, err = loadServingConfig(servingConfigFile)
	if err != nil {
		return API{}, err
	}
	return config, nil
}

// LoadAPIBootstrap loads only the L0-owned transport contract. Bootstrap mode
// is deliberately live-but-not-ready and rejects every business RPC, so an L0
// release can be deployed before any L1 installation publishes serving
// authority. The stopped activation step still uses LoadAPI and therefore
// cannot serve without the complete immutable authority set.
func LoadAPIBootstrap(lookup LookupEnv) (API, error) {
	return loadAPITransport(lookup)
}

func loadAPITransport(lookup LookupEnv) (API, error) {
	lookup = normalizeLookup(lookup)
	config := API{
		AdminAddress:       valueOr(lookup, RuntimeAPIAdminAddressEnv, DefaultRuntimeAPIAdminAddress),
		GRPCAddress:        valueOr(lookup, RuntimeAPIGRPCAddressEnv, DefaultRuntimeAPIGRPCAddress),
		TLSCertificateFile: valueOr(lookup, RuntimeAPITLSCertificateFileEnv, ""),
		TLSPrivateKeyFile:  valueOr(lookup, RuntimeAPITLSPrivateKeyFileEnv, ""),
	}
	limit, err := ParseAdmissionLimit(valueOr(lookup, RuntimeAPIAdmissionLimitEnv, ""))
	if err != nil {
		return API{}, err
	}
	config.AdmissionLimit = limit
	profiling, err := strconv.ParseBool(valueOr(lookup, RuntimeAPIProfilingEnabledEnv, "false"))
	if err != nil {
		return API{}, fmt.Errorf("runtime config: %s must be true or false", RuntimeAPIProfilingEnabledEnv)
	}
	config.ProfilingEnabled = profiling
	sqlDiagnostics, err := strconv.ParseBool(valueOr(lookup, RuntimeAPISQLDiagnosticsEnabledEnv, "false"))
	if err != nil {
		return API{}, fmt.Errorf("runtime config: %s must be true or false", RuntimeAPISQLDiagnosticsEnabledEnv)
	}
	config.SQLDiagnosticsEnabled = sqlDiagnostics
	if err := validateListenAddress(RuntimeAPIAdminAddressEnv, config.AdminAddress); err != nil {
		return API{}, err
	}
	if err := validateListenAddress(RuntimeAPIGRPCAddressEnv, config.GRPCAddress); err != nil {
		return API{}, err
	}
	if config.AdminAddress == config.GRPCAddress {
		return API{}, errors.New("runtime config: admin and gRPC addresses must be distinct")
	}
	if err := validateRegularFile(RuntimeAPITLSCertificateFileEnv, config.TLSCertificateFile); err != nil {
		return API{}, err
	}
	if err := validateOwnerFile(RuntimeAPITLSPrivateKeyFileEnv, config.TLSPrivateKeyFile); err != nil {
		return API{}, err
	}
	return config, nil
}

func LoadWorker(lookup LookupEnv) (Worker, error) {
	lookup = normalizeLookup(lookup)
	config := Worker{
		AdminAddress:              valueOr(lookup, RuntimeWorkerAdminAddressEnv, DefaultWorkerAdminAddress),
		WorkloadIdentityTokenFile: valueOr(lookup, WorkloadIdentityTokenFileEnv, ""),
	}
	roleConfigFile := valueOr(lookup, RuntimeWorkerConfigFileEnv, "")
	catalogReconcileConfigFile := valueOr(lookup, RuntimeWorkerCatalogReconcileConfigFileEnv, "")
	timedFlowOwnerConfigFile := valueOr(lookup, RuntimeWorkerTimedFlowOwnerConfigFileEnv, "")
	if err := validateListenAddress(RuntimeWorkerAdminAddressEnv, config.AdminAddress); err != nil {
		return Worker{}, err
	}
	if err := validateIdentityTokenFile(config.WorkloadIdentityTokenFile); err != nil {
		return Worker{}, err
	}
	if err := validateRegularFile(RuntimeWorkerConfigFileEnv, roleConfigFile); err != nil {
		return Worker{}, err
	}
	if catalogReconcileConfigFile != "" {
		if err := validateRegularFile(RuntimeWorkerCatalogReconcileConfigFileEnv, catalogReconcileConfigFile); err != nil {
			return Worker{}, err
		}
		config.CatalogReconcileConfigFile = catalogReconcileConfigFile
	}
	var err error
	config.Role, err = loadWorkerRoleConfig(roleConfigFile)
	if err != nil {
		return Worker{}, err
	}
	if timedFlowOwnerConfigFile != "" {
		if err := validateRegularFile(RuntimeWorkerTimedFlowOwnerConfigFileEnv, timedFlowOwnerConfigFile); err != nil {
			return Worker{}, err
		}
		timedFlowOwner, err := loadWorkerRoleConfig(timedFlowOwnerConfigFile)
		if err != nil {
			return Worker{}, fmt.Errorf("runtime config: load timed Flow owner role: %w", err)
		}
		config.TimedFlowOwner = &timedFlowOwner
	}
	return config, nil
}

var (
	workerFamilyPattern        = regexp.MustCompile(`^[a-z][a-z0-9.-]{0,63}$`)
	workerDeploymentPattern    = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:/@-]{0,254}$`)
	workerDigestPattern        = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`)
	exactSourceRevisionPattern = regexp.MustCompile(`^[0-9a-f]{40}$`)
)

func loadWorkerRoleConfig(path string) (WorkerRole, error) {
	raw, err := os.ReadFile(path)
	if err != nil {
		return WorkerRole{}, fmt.Errorf("runtime config: read worker role config: %w", err)
	}
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.DisallowUnknownFields()
	var config WorkerRole
	if err := decoder.Decode(&config); err != nil {
		return WorkerRole{}, fmt.Errorf("runtime config: decode worker role config: %w", err)
	}
	var extra any
	if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) {
		return WorkerRole{}, errors.New("runtime config: worker role config must contain exactly one JSON document")
	}
	if err := config.Validate(); err != nil {
		return WorkerRole{}, err
	}
	return config, nil
}

func (config WorkerRole) Validate() error {
	if (config.Schema != WorkerConfigSchemaV1 &&
		config.Schema != WorkerConfigSchemaV2 &&
		config.Schema != WorkerConfigSchemaV3) ||
		!workerFamilyPattern.MatchString(config.WorkFamily) ||
		!workerDeploymentPattern.MatchString(config.DeploymentID) || !workerDigestPattern.MatchString(config.WorkerReleaseDigest) ||
		!exactSourceRevisionPattern.MatchString(config.WorkerSourceRevision) ||
		config.OwnershipEpoch == 0 || config.BatchSize <= 0 || config.BatchSize > MaxWorkerBatchSize ||
		config.Parallelism < 0 || config.Parallelism > MaxWorkerParallelism ||
		config.MaxAttempts <= 0 || config.MaxAttempts > MaxWorkerAttempts ||
		config.DBMaxOpenConns < 2 || config.DBMaxOpenConns > MaxWorkerDBConnections ||
		config.DBMaxIdleConns <= 0 || config.DBMaxIdleConns > config.DBMaxOpenConns {
		return errors.New("runtime config: worker role config is incomplete or outside enforced bounds")
	}
	if 2*config.EffectiveParallelism()+1 > config.DBMaxOpenConns {
		return errors.New("runtime config: worker database pool cannot sustain effect lanes, heartbeats, and controller work")
	}
	for _, duration := range []struct {
		name    string
		raw     string
		minimum time.Duration
		maximum time.Duration
	}{
		{name: "poll interval", raw: config.PollInterval, minimum: 50 * time.Millisecond, maximum: MaxWorkerPollInterval},
		{name: "lease duration", raw: config.LeaseDuration, minimum: time.Second, maximum: 5 * time.Minute},
		{name: "retry jitter", raw: config.RetryJitterMax, minimum: time.Millisecond, maximum: 5 * time.Second},
		{name: "shutdown timeout", raw: config.ShutdownTimeout, minimum: time.Second, maximum: time.Minute},
	} {
		parsed, parseErr := time.ParseDuration(duration.raw)
		if parseErr != nil || parsed < duration.minimum || parsed > duration.maximum {
			return fmt.Errorf("runtime config: worker %s must be between %s and %s", duration.name, duration.minimum, duration.maximum)
		}
	}
	if config.OwnershipHealthInterval != "" {
		interval, parseErr := time.ParseDuration(config.OwnershipHealthInterval)
		if parseErr != nil || interval < time.Second || interval > 5*time.Minute {
			return errors.New("runtime config: worker ownership health interval must be between 1s and 5m")
		}
	}
	if config.ProcessedRetention != "" {
		retention, parseErr := time.ParseDuration(config.ProcessedRetention)
		if parseErr != nil || retention < time.Second {
			return errors.New("runtime config: worker processed retention must be at least one second when present")
		}
	}
	if config.ProcessedCleanupInterval != "" {
		interval, parseErr := time.ParseDuration(config.ProcessedCleanupInterval)
		if parseErr != nil || interval < time.Second {
			return errors.New("runtime config: worker processed cleanup interval must be at least one second when present")
		}
	}
	switch config.Schema {
	case WorkerConfigSchemaV1:
		if config.PermissionStateProtocol != "" {
			return errors.New("runtime config: worker role v1 cannot declare Permission State protocol")
		}
		if config.hasMetadataScope() {
			return errors.New("runtime config: payload worker role cannot declare metadata scope")
		}
		return nil
	case WorkerConfigSchemaV2:
		if config.PermissionStateProtocol != "" {
			return errors.New("runtime config: worker role v2 cannot declare Permission State protocol")
		}
		if _, err := config.MetadataScope(); err != nil {
			return err
		}
		return nil
	case WorkerConfigSchemaV3:
		if config.PermissionStateProtocol != PermissionStateProtocolV1 {
			return errors.New("runtime config: worker role v3 requires Permission State v1")
		}
		if !config.hasMetadataScope() {
			return nil
		}
		if _, err := config.MetadataScope(); err != nil {
			return err
		}
		return nil
	}
	return errors.New("runtime config: worker role schema is unsupported")
}

func (config WorkerRole) EffectiveOwnershipHealthInterval() time.Duration {
	if config.OwnershipHealthInterval == "" {
		return DefaultWorkerOwnershipHealthInterval
	}
	interval, err := time.ParseDuration(config.OwnershipHealthInterval)
	if err != nil {
		return 0
	}
	return interval
}

func (config WorkerRole) EffectiveParallelism() int {
	if config.Parallelism <= 0 {
		return 1
	}
	return config.Parallelism
}

func (config WorkerRole) hasMetadataScope() bool {
	return config.OrgID != "" || config.AppInstallationID != "" || config.AppCapabilityID != "" || config.RuntimeCapabilityID != "" ||
		config.ExpectedCompatibilityReceiptDigest != "" || config.CatalogSnapshotStoreRoot != "" ||
		config.SchemaGeneration != 0 || config.EngineGeneration != 0
}

// HasMetadataScope reports whether the role declares any immutable metadata
// scope field. Work-family owners use it to keep payload-only v3 roles from
// accidentally acquiring metadata authority.
func (config WorkerRole) HasMetadataScope() bool {
	return config.hasMetadataScope()
}

func (config WorkerRole) MetadataScope() (WorkerMetadataScope, error) {
	if config.Schema != WorkerConfigSchemaV2 &&
		config.Schema != WorkerConfigSchemaV3 {
		return WorkerMetadataScope{}, errors.New("runtime config: metadata worker role requires a metadata-capable schema")
	}
	scope := WorkerMetadataScope{
		OrgID: config.OrgID, AppInstallationID: config.AppInstallationID,
		AppCapabilityID:                    config.AppCapabilityID,
		RuntimeCapabilityID:                config.RuntimeCapabilityID,
		ExpectedCompatibilityReceiptDigest: config.ExpectedCompatibilityReceiptDigest,
		CatalogSnapshotStoreRoot:           config.CatalogSnapshotStoreRoot,
		SchemaGeneration:                   config.SchemaGeneration, EngineGeneration: config.EngineGeneration,
	}
	if err := scope.Validate(); err != nil {
		return WorkerMetadataScope{}, err
	}
	return scope, nil
}

func (config WorkerMetadataScope) Validate() error {
	for name, value := range map[string]string{
		"org": config.OrgID, "App installation": config.AppInstallationID,
		"Runtime capability": config.RuntimeCapabilityID,
	} {
		if value == "" || strings.TrimSpace(value) != value || len(value) > 255 {
			return fmt.Errorf("runtime config: worker metadata %s identity is invalid", name)
		}
	}
	if config.AppCapabilityID != "" &&
		(strings.TrimSpace(config.AppCapabilityID) != config.AppCapabilityID ||
			len(config.AppCapabilityID) > 255) {
		return errors.New("runtime config: worker metadata App capability identity is invalid")
	}
	if config.ExpectedCompatibilityReceiptDigest != "" &&
		!workerDigestPattern.MatchString(config.ExpectedCompatibilityReceiptDigest) {
		return errors.New("runtime config: worker metadata compatibility receipt digest is invalid")
	}
	if !filepath.IsAbs(config.CatalogSnapshotStoreRoot) || filepath.Clean(config.CatalogSnapshotStoreRoot) != config.CatalogSnapshotStoreRoot {
		return errors.New("runtime config: worker Catalog snapshot store root must be an absolute canonical path")
	}
	if config.SchemaGeneration == 0 || config.EngineGeneration == 0 {
		return errors.New("runtime config: worker metadata compatibility generations are required")
	}
	return nil
}

func ParseAdmissionLimit(raw string) (int, error) {
	if raw == "" {
		return DefaultAPIAdmissionLimit, nil
	}
	value, err := strconv.Atoi(raw)
	if err != nil || value <= 0 || value > MaxAPIAdmissionLimit {
		return 0, fmt.Errorf("runtime config: %s must be between 1 and %d", RuntimeAPIAdmissionLimitEnv, MaxAPIAdmissionLimit)
	}
	return value, nil
}

func normalizeLookup(lookup LookupEnv) LookupEnv {
	if lookup == nil {
		return os.LookupEnv
	}
	return lookup
}

func valueOr(lookup LookupEnv, key, fallback string) string {
	if value, ok := lookup(key); ok && value != "" {
		return value
	}
	return fallback
}

func validateListenAddress(name, address string) error {
	if strings.TrimSpace(address) != address || address == "" {
		return fmt.Errorf("runtime config: %s must be a canonical host:port address", name)
	}
	_, port, err := net.SplitHostPort(address)
	if err != nil {
		return fmt.Errorf("runtime config: %s must be a canonical host:port address: %w", name, err)
	}
	parsed, err := strconv.Atoi(port)
	if err != nil || parsed <= 0 || parsed > 65535 {
		return fmt.Errorf("runtime config: %s port must be between 1 and 65535", name)
	}
	return nil
}

func validateIdentityTokenFile(path string) error {
	return validateOwnerFile(WorkloadIdentityTokenFileEnv, path)
}

func validateOwnerFile(name, path string) error {
	if err := validateRegularFile(name, path); err != nil {
		return err
	}
	info, _ := os.Stat(path)
	if info.Mode().Perm()&0o007 != 0 {
		return fmt.Errorf("runtime config: %s must not be world-accessible", name)
	}
	return nil
}

func validateRegularFile(name, path string) error {
	if path == "" {
		return fmt.Errorf("runtime config: %s is required", name)
	}
	if !filepath.IsAbs(path) || filepath.Clean(path) != path {
		return fmt.Errorf("runtime config: %s must be an absolute canonical path", name)
	}
	info, err := os.Stat(path)
	if err != nil {
		return fmt.Errorf("runtime config: stat %s: %w", name, err)
	}
	if !info.Mode().IsRegular() || info.Size() == 0 {
		return fmt.Errorf("runtime config: %s must be a non-empty regular file", name)
	}
	return nil
}

func loadServingConfig(path string) (Serving, error) {
	raw, err := os.ReadFile(path)
	if err != nil {
		return Serving{}, fmt.Errorf("runtime config: read serving config: %w", err)
	}
	var discriminator struct {
		Schema string `json:"schema"`
	}
	if err := decodeServingDocument(raw, &discriminator, false); err != nil {
		return Serving{}, err
	}
	if discriminator.Schema != ServingConfigSchemaV2 &&
		discriminator.Schema != ServingConfigSchemaV3 &&
		discriminator.Schema != ServingConfigSchemaV4 {
		return Serving{}, errors.New("runtime config: serving config schema is unsupported")
	}
	var config Serving
	if err := decodeServingDocument(raw, &config, true); err != nil {
		return Serving{}, err
	}

	if config.ReplicaID == "" ||
		config.OrgID == "" || config.AppInstallationID == "" ||
		config.ExpectedServiceReleaseDigest == "" || len(config.Capabilities) == 0 ||
		config.SchemaGeneration == 0 || config.EngineGeneration == 0 || len(config.SessionScopeCapabilities) == 0 {
		return Serving{}, errors.New("runtime config: serving config is incomplete")
	}
	switch config.Schema {
	case ServingConfigSchemaV2:
		if config.PermissionStateProtocol != "" ||
			len(config.AuthorizedWorkloadIDs) != 0 ||
			len(config.CatalogConsumerCapabilities) != 0 ||
			len(config.FederatedOIDCProviders) != 0 {
			return Serving{}, errors.New(
				"runtime config: serving config v2 cannot declare v3/v4-only authority fields",
			)
		}
	case ServingConfigSchemaV3:
		if config.PermissionStateProtocol != PermissionStateProtocolV1 ||
			len(config.AuthorizedWorkloadIDs) != 0 ||
			len(config.CatalogConsumerCapabilities) != 0 ||
			len(config.FederatedOIDCProviders) != 0 {
			return Serving{}, errors.New(
				"runtime config: serving config v3 requires Permission State v1 and cannot declare v4-only authority fields",
			)
		}
	case ServingConfigSchemaV4:
		if config.PermissionStateProtocol != PermissionStateProtocolV1 ||
			config.AuthorizedWorkloadID != "" ||
			len(config.AuthorizedWorkloadIDs) < 2 {
			return Serving{}, errors.New(
				"runtime config: serving config v4 requires Permission State v1 and multiple explicit workloads",
			)
		}
	}
	workloads := make(map[string]struct{}, len(config.AuthorizedWorkloadIDs)+1)
	if config.Schema == ServingConfigSchemaV4 {
		for index, workloadID := range config.AuthorizedWorkloadIDs {
			if strings.TrimSpace(workloadID) != workloadID || workloadID == "" ||
				(index > 0 && config.AuthorizedWorkloadIDs[index-1] >= workloadID) {
				return Serving{}, errors.New(
					"runtime config: authorized workloads must be sorted, unique, and canonical",
				)
			}
			workloads[workloadID] = struct{}{}
		}
	} else {
		if config.AuthorizedWorkloadID == "" {
			return Serving{}, errors.New("runtime config: serving config is incomplete")
		}
		workloads[config.AuthorizedWorkloadID] = struct{}{}
	}
	if !filepath.IsAbs(config.CatalogSnapshotStoreRoot) || filepath.Clean(config.CatalogSnapshotStoreRoot) != config.CatalogSnapshotStoreRoot {
		return Serving{}, errors.New("runtime config: catalog snapshot store root must be an absolute canonical path")
	}
	if err := validatePositiveDuration(config.IdentityAttemptCap); err != nil {
		return Serving{}, errors.New("runtime config: serving attempt durations must be explicit and positive")
	}
	appCapabilities := make(map[string]struct{}, len(config.Capabilities))
	runtimeCapabilities := make(map[string]struct{}, len(config.Capabilities))
	for _, capability := range config.Capabilities {
		if capability.AppCapabilityID == "" || capability.RuntimeCapabilityID == "" ||
			capability.AppCapabilityID == capability.RuntimeCapabilityID || capability.ExpectedOwnershipEpoch == 0 ||
			capability.ExpectedCompatibilityReceiptDigest == "" {
			return Serving{}, errors.New("runtime config: serving capability is incomplete")
		}
		workloadID := capability.AuthorizedWorkloadID
		if config.Schema != ServingConfigSchemaV4 {
			if workloadID != "" {
				return Serving{}, errors.New(
					"runtime config: serving capability workload requires config v4",
				)
			}
			workloadID = config.AuthorizedWorkloadID
		}
		if _, authorized := workloads[workloadID]; !authorized {
			return Serving{}, errors.New(
				"runtime config: serving capability workload is not authorized",
			)
		}
		if _, duplicate := appCapabilities[capability.AppCapabilityID]; duplicate {
			return Serving{}, errors.New("runtime config: App capability is selected more than once")
		}
		if _, duplicate := runtimeCapabilities[capability.RuntimeCapabilityID]; duplicate {
			return Serving{}, errors.New("runtime config: Runtime capability is selected more than once")
		}
		appCapabilities[capability.AppCapabilityID] = struct{}{}
		runtimeCapabilities[capability.RuntimeCapabilityID] = struct{}{}
		if err := validatePositiveDuration(capability.AdmissionLease); err != nil {
			return Serving{}, errors.New("runtime config: serving attempt durations must be explicit and positive")
		}
		if capability.OutcomeRetention != "" {
			if err := validatePositiveDuration(capability.OutcomeRetention); err != nil {
				return Serving{}, errors.New("runtime config: serving outcome retention must be explicit and positive when present")
			}
		}
	}
	for index, capability := range config.WorkloadCapabilities {
		if config.Schema != ServingConfigSchemaV4 ||
			capability.RuntimeCapabilityID == "" ||
			capability.AuthorizedWorkloadID == "" {
			return Serving{}, errors.New(
				"runtime config: workload capability is incomplete",
			)
		}
		if _, authorized := workloads[capability.AuthorizedWorkloadID]; !authorized {
			return Serving{}, errors.New(
				"runtime config: workload capability consumer is not authorized",
			)
		}
		if index > 0 && config.WorkloadCapabilities[index-1].RuntimeCapabilityID >=
			capability.RuntimeCapabilityID {
			return Serving{}, errors.New(
				"runtime config: workload capabilities must be sorted and unique",
			)
		}
		if _, duplicate := runtimeCapabilities[capability.RuntimeCapabilityID]; duplicate {
			return Serving{}, errors.New(
				"runtime config: Runtime capability is selected more than once",
			)
		}
		if capability.AdmissionLease != "" {
			if err := validatePositiveDuration(capability.AdmissionLease); err != nil {
				return Serving{}, errors.New(
					"runtime config: workload capability admission lease must be explicit and positive when present",
				)
			}
		}
		if capability.RequiredCustomPermission != "" && capability.RequiredSystemPermission != "" {
			return Serving{}, errors.New("runtime config: workload capability must select one permission namespace")
		}
		for _, permission := range []string{capability.RequiredCustomPermission, capability.RequiredSystemPermission} {
			if permission != "" && (len(permission) > 255 || strings.TrimSpace(permission) != permission || strings.ContainsAny(permission, "\x00\r\n")) {
				return Serving{}, errors.New("runtime config: workload capability permission must be canonical")
			}
		}
		runtimeCapabilities[capability.RuntimeCapabilityID] = struct{}{}
	}
	for _, capability := range config.CatalogConsumerCapabilities {
		if config.Schema != ServingConfigSchemaV4 ||
			capability.RuntimeCapabilityID == "" ||
			capability.AuthorizedWorkloadID == "" {
			return Serving{}, errors.New(
				"runtime config: Catalog consumer capability is incomplete",
			)
		}
		if _, authorized := workloads[capability.AuthorizedWorkloadID]; !authorized {
			return Serving{}, errors.New(
				"runtime config: Catalog consumer workload is not authorized",
			)
		}
		if _, duplicate := runtimeCapabilities[capability.RuntimeCapabilityID]; duplicate {
			return Serving{}, errors.New(
				"runtime config: Runtime capability is selected more than once",
			)
		}
		runtimeCapabilities[capability.RuntimeCapabilityID] = struct{}{}
		if err := validatePositiveDuration(capability.AdmissionLease); err != nil {
			return Serving{}, errors.New(
				"runtime config: serving attempt durations must be explicit and positive",
			)
		}
	}
	for index, provider := range config.FederatedOIDCProviders {
		if config.Schema != ServingConfigSchemaV4 ||
			provider.ProviderID == "" || strings.TrimSpace(provider.ProviderID) != provider.ProviderID ||
			len(provider.ProviderID) > 128 || provider.Audience == "" ||
			strings.TrimSpace(provider.Audience) != provider.Audience || len(provider.Audience) > 512 ||
			provider.SessionScope == "" || strings.TrimSpace(provider.SessionScope) != provider.SessionScope ||
			len(strings.Fields(provider.SessionScope)) != 1 ||
			len(provider.TrustedIssuers) == 0 || provider.JWKSURL == "" ||
			(index > 0 && config.FederatedOIDCProviders[index-1].ProviderID >= provider.ProviderID) {
			return Serving{}, errors.New(
				"runtime config: federated OIDC providers must be sorted, unique, canonical, and complete",
			)
		}
		if _, found := config.SessionScopeCapabilities[provider.SessionScope]; !found {
			return Serving{}, errors.New(
				"runtime config: federated OIDC provider session scope has no capability policy",
			)
		}
		for issuerIndex, issuer := range provider.TrustedIssuers {
			if issuer == "" || strings.TrimSpace(issuer) != issuer || len(issuer) > 2048 ||
				(issuerIndex > 0 && provider.TrustedIssuers[issuerIndex-1] >= issuer) {
				return Serving{}, errors.New(
					"runtime config: federated OIDC issuers must be sorted, unique, and canonical",
				)
			}
		}
		parsedJWKSURL, err := url.Parse(provider.JWKSURL)
		if err != nil || parsedJWKSURL.Scheme != "https" || parsedJWKSURL.Host == "" ||
			parsedJWKSURL.User != nil || parsedJWKSURL.Fragment != "" || parsedJWKSURL.String() != provider.JWKSURL {
			return Serving{}, errors.New(
				"runtime config: federated OIDC JWKS URL must be canonical HTTPS",
			)
		}
	}
	if len(config.UserAPICallers) != 0 && config.Schema != ServingConfigSchemaV4 {
		return Serving{}, errors.New("runtime config: user API callers require v4")
	}
	for i, caller := range config.UserAPICallers {
		if caller == "" || strings.TrimSpace(caller) != caller || (i > 0 && config.UserAPICallers[i-1] >= caller) {
			return Serving{}, errors.New("runtime config: user API callers must be sorted, unique, and canonical")
		}
		if _, found := workloads[caller]; !found {
			return Serving{}, errors.New("runtime config: user API caller is not authenticated by the workload authority")
		}
		for _, c := range config.Capabilities {
			if c.AuthorizedWorkloadID == caller {
				return Serving{}, errors.New("runtime config: a user API caller cannot own a handler")
			}
		}
		for _, c := range config.WorkloadCapabilities {
			if c.AuthorizedWorkloadID == caller {
				return Serving{}, errors.New("runtime config: a user API caller cannot also be a workload consumer")
			}
		}
		for _, c := range config.CatalogConsumerCapabilities {
			if c.AuthorizedWorkloadID == caller {
				return Serving{}, errors.New("runtime config: a user API caller cannot also be a Catalog consumer")
			}
		}
	}

	return config, nil
}

func decodeServingDocument(raw []byte, target any, rejectUnknownFields bool) error {
	decoder := json.NewDecoder(bytes.NewReader(raw))
	if rejectUnknownFields {
		decoder.DisallowUnknownFields()
	}
	if err := decoder.Decode(target); err != nil {
		return fmt.Errorf("runtime config: decode serving config: %w", err)
	}
	var extra any
	if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) {
		return errors.New("runtime config: serving config must contain exactly one JSON document")
	}
	return nil
}

func validatePositiveDuration(rawDuration string) error {
	duration, parseErr := time.ParseDuration(rawDuration)
	if parseErr != nil || duration <= 0 {
		return errors.New("duration must be explicit and positive")
	}
	return nil
}
