{{- /* Cross-field validation that JSON Schema cannot express reliably. */ -}}
{{- $absoluteHttpUrlPattern := "^https?://(\\[[0-9A-Fa-f:.]+\\]|[A-Za-z0-9]([A-Za-z0-9.-]*[A-Za-z0-9])?)(:[0-9]{1,5})?([/?#][^[:space:]]*)?$" -}}
{{- if not .Values.server.enabled -}}
{{- fail "server.enabled=false is unsupported because the bundled web component requires the SkillHub server" -}}
{{- end -}}
{{- if and .Values.auth.direct.enabled (not .Values.auth.direct.provider) -}}
{{- fail "auth.direct.enabled=true requires auth.direct.provider" -}}
{{- end -}}
{{- if and .Values.auth.accountMerge.enabled (ne .Values.session.repositoryType "indexed") -}}
{{- fail "auth.accountMerge.enabled=true requires session.repositoryType=indexed" -}}
{{- end -}}
{{- if and .Values.auth.accountMerge.enabled (not .Values.auth.accountMerge.sessionCutoverComplete) -}}
{{- fail "auth.accountMerge.enabled=true requires auth.accountMerge.sessionCutoverComplete=true after rotating the Spring Session namespace" -}}
{{- end -}}
{{- if .Values.auth.ldap.enabled -}}
{{- if not .Values.auth.ldap.url -}}
{{- fail "auth.ldap.enabled=true requires auth.ldap.url" -}}
{{- end -}}
{{- if not .Values.auth.ldap.baseDn -}}
{{- fail "auth.ldap.enabled=true requires auth.ldap.baseDn" -}}
{{- end -}}
{{- if not .Values.auth.ldap.bindDn -}}
{{- fail "auth.ldap.enabled=true requires auth.ldap.bindDn" -}}
{{- end -}}
{{- if and (not .Values.existingSecret) (not .Values.secrets.ldapBindPassword) -}}
{{- fail "auth.ldap.enabled=true requires secrets.ldapBindPassword or existingSecret key ldap-bind-password" -}}
{{- end -}}
{{- if and (eq .Values.auth.ldap.directoryType "CUSTOM") (or (not .Values.auth.ldap.subjectAttribute) (not .Values.auth.ldap.subjectType)) -}}
{{- fail "auth.ldap.directoryType=CUSTOM requires subjectAttribute and subjectType" -}}
{{- end -}}
{{- if hasPrefix "ldaps://" .Values.auth.ldap.url -}}
{{- if .Values.auth.ldap.startTls -}}
{{- fail "auth.ldap.startTls must be false for an ldaps URL" -}}
{{- end -}}
{{- else if hasPrefix "ldap://" .Values.auth.ldap.url -}}
{{- if not .Values.auth.ldap.startTls -}}
{{- fail "auth.ldap.startTls must be true for an ldap URL" -}}
{{- end -}}
{{- else -}}
{{- fail "auth.ldap.url must start with ldap:// or ldaps://" -}}
{{- end -}}
{{- if regexMatch "[?#]" .Values.auth.ldap.url -}}
{{- fail "auth.ldap.url must not contain a query or fragment" -}}
{{- end -}}
{{- end -}}
{{- if .Values.auth.cas.enabled -}}
{{- if not .Values.auth.cas.serverUrl -}}
{{- fail "auth.cas.enabled=true requires auth.cas.serverUrl" -}}
{{- end -}}
{{- if not .Values.auth.cas.serviceUrl -}}
{{- fail "auth.cas.enabled=true requires auth.cas.serviceUrl" -}}
{{- end -}}
{{- if not (regexMatch "^https://[^[:space:]]+$" .Values.auth.cas.serverUrl) -}}
{{- fail "auth.cas.serverUrl must be an absolute HTTPS URL" -}}
{{- end -}}
{{- if not (regexMatch "^https://[^[:space:]?#]+$" .Values.auth.cas.serviceUrl) -}}
{{- fail "auth.cas.serviceUrl must be an absolute HTTPS URL without query or fragment" -}}
{{- end -}}
{{- $expectedCasCallback := printf "/api/v1/auth/cas/%s/callback" .Values.auth.cas.providerCode -}}
{{- if not (hasSuffix $expectedCasCallback .Values.auth.cas.serviceUrl) -}}
{{- fail (printf "auth.cas.serviceUrl must end with %s" $expectedCasCallback) -}}
{{- end -}}
{{- end -}}

{{- if .Values.auth.dingtalk.enabled -}}
{{- if and (not .Values.existingSecret) (not .Values.secrets.oauth2DingtalkClientId) -}}
{{- fail "auth.dingtalk.enabled=true requires secrets.oauth2DingtalkClientId or existingSecret key oauth2-dingtalk-client-id" -}}
{{- end -}}
{{- if and (not .Values.existingSecret) (not .Values.secrets.oauth2DingtalkClientSecret) -}}
{{- fail "auth.dingtalk.enabled=true requires secrets.oauth2DingtalkClientSecret or existingSecret key oauth2-dingtalk-client-secret" -}}
{{- end -}}
{{- end -}}

{{- if and .Values.ingress.enabled (not .Values.server.service.enabled) -}}
{{- fail "ingress.enabled=true requires server.service.enabled=true" -}}
{{- end -}}
{{- if and .Values.ingress.enabled (not .Values.web.service.enabled) -}}
{{- fail "ingress.enabled=true requires web.service.enabled=true" -}}
{{- end -}}
{{- if and .Values.ingress.enabled .Values.ingress.certManager.enabled (not .Values.ingress.tls) -}}
{{- fail "ingress.certManager.enabled=true requires at least one ingress.tls entry" -}}
{{- end -}}
{{- range $host := .Values.ingress.hosts -}}
{{- range $path := $host.paths -}}
{{- if regexMatch "^/(api|oauth2|login/oauth2|\\.well-known)(/|$)" $path.path -}}
{{- fail "ingress.hosts[].paths reserves /api, /oauth2, /login/oauth2 and /.well-known for the SkillHub server" -}}
{{- end -}}
{{- end -}}
{{- end -}}

{{- range $name := list "server" "web" "scanner" -}}
{{- $component := index $.Values $name -}}
{{- $enabled := true -}}
{{- if hasKey $component "enabled" -}}
{{- $enabled = $component.enabled -}}
{{- end -}}
{{- if and $enabled $component.autoscaling.enabled -}}
{{- if gt ($component.autoscaling.minReplicas | int) ($component.autoscaling.maxReplicas | int) -}}
{{- fail (printf "%s.autoscaling.minReplicas must not exceed maxReplicas" $name) -}}
{{- end -}}
{{- if and (not $component.autoscaling.targetCPUUtilizationPercentage) (not $component.autoscaling.targetMemoryUtilizationPercentage) -}}
{{- fail (printf "%s.autoscaling requires at least one CPU or memory utilization target" $name) -}}
{{- end -}}
{{- end -}}
{{- end -}}

{{- $localStorageReplicas := .Values.server.replicaCount | int -}}
{{- if .Values.server.autoscaling.enabled -}}
{{- $localStorageReplicas = .Values.server.autoscaling.maxReplicas | int -}}
{{- end -}}
{{- if and (not .Values.s3.enabled) (gt $localStorageReplicas 1) -}}
{{- if not .Values.server.storage.accessMode -}}
{{- fail "local storage with multiple server replicas requires server.storage.accessMode=ReadWriteMany and an RWX-capable StorageClass; use S3 otherwise" -}}
{{- end -}}
{{- if ne .Values.server.storage.accessMode "ReadWriteMany" -}}
{{- fail "local storage with multiple server replicas requires server.storage.accessMode=ReadWriteMany" -}}
{{- end -}}
{{- end -}}

{{- if and (not .Values.postgresql.enabled) (not .Values.externalDatabase.host) -}}
{{- fail "postgresql.enabled=false requires externalDatabase.host for dependency checks" -}}
{{- end -}}
{{- if and .Values.externalRedis.sentinel.enabled .Values.externalRedis.cluster.enabled -}}
{{- fail "externalRedis.sentinel.enabled and externalRedis.cluster.enabled are mutually exclusive" -}}
{{- end -}}
{{- if and .Values.redis.enabled .Values.externalRedis.cluster.enabled -}}
{{- fail "externalRedis.cluster.enabled=true requires redis.enabled=false" -}}
{{- end -}}
{{- if and (not .Values.redis.enabled) (not .Values.externalRedis.sentinel.enabled) (not .Values.externalRedis.cluster.enabled) (not .Values.externalRedis.host) -}}
{{- fail "redis.enabled=false requires externalRedis.host" -}}
{{- end -}}
{{- if and (not .Values.redis.enabled) .Values.externalRedis.sentinel.enabled (not .Values.externalRedis.sentinel.nodes) -}}
{{- fail "external Redis Sentinel requires at least one externalRedis.sentinel.nodes entry" -}}
{{- end -}}
{{- if and (not .Values.redis.enabled) .Values.externalRedis.cluster.enabled (not .Values.externalRedis.cluster.nodes) -}}
{{- fail "external Redis Cluster requires at least one externalRedis.cluster.nodes entry" -}}
{{- end -}}
{{- if and (not .Values.redis.enabled) .Values.externalRedis.cluster.enabled -}}
{{- range $node := .Values.externalRedis.cluster.nodes -}}
{{- if not (regexMatch "^[A-Za-z0-9._-]+:[0-9]{1,5}$" $node) -}}
{{- fail (printf "externalRedis.cluster.nodes entry must use host:port: %s" $node) -}}
{{- end -}}
{{- $parts := splitList ":" $node -}}
{{- $port := last $parts | int -}}
{{- if or (lt $port 1) (gt $port 65535) -}}
{{- fail (printf "externalRedis.cluster.nodes port must be between 1 and 65535: %s" $node) -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if and .Values.s3.endpoint (not (regexMatch $absoluteHttpUrlPattern .Values.s3.endpoint)) -}}
{{- fail "s3.endpoint must be an absolute HTTP(S) URL" -}}
{{- end -}}
{{- if and .Values.s3.publicEndpoint (not (regexMatch $absoluteHttpUrlPattern .Values.s3.publicEndpoint)) -}}
{{- fail "s3.publicEndpoint must be an absolute HTTP(S) URL" -}}
{{- end -}}

{{- if not .Values.secrets.allowAutoGenerated -}}
{{- if not .Values.existingSecret -}}
{{- if not (or .Values.secrets.bootstrapAdminPassword .Values.bootstrapAdmin.password) -}}
{{- fail "secrets.allowAutoGenerated=false requires secrets.bootstrapAdminPassword or bootstrapAdmin.password" -}}
{{- end -}}
{{- if not .Values.secrets.downloadAnonCookieSecret -}}
{{- fail "secrets.allowAutoGenerated=false requires secrets.downloadAnonCookieSecret" -}}
{{- end -}}
{{- end -}}
{{- if and .Values.postgresql.enabled (not .Values.postgresql.auth.existingSecret) -}}
{{- if and .Values.postgresql.auth.enablePostgresUser (not .Values.postgresql.auth.postgresPassword) -}}
{{- fail "secrets.allowAutoGenerated=false requires postgresql.auth.postgresPassword or postgresql.auth.existingSecret" -}}
{{- end -}}
{{- if and .Values.postgresql.auth.username (ne .Values.postgresql.auth.username "postgres") (not .Values.postgresql.auth.password) -}}
{{- fail "secrets.allowAutoGenerated=false requires postgresql.auth.password or postgresql.auth.existingSecret" -}}
{{- end -}}
{{- if and (eq .Values.postgresql.architecture "replication") (not .Values.postgresql.auth.replicationPassword) -}}
{{- fail "secrets.allowAutoGenerated=false requires postgresql.auth.replicationPassword for replication architecture" -}}
{{- end -}}
{{- end -}}
{{- if and .Values.redis.enabled .Values.redis.auth.enabled (not .Values.redis.auth.existingSecret) (not .Values.redis.auth.password) -}}
{{- fail "secrets.allowAutoGenerated=false requires redis.auth.password or redis.auth.existingSecret" -}}
{{- end -}}
{{- end -}}
