package controlplane

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/nexuscrm/db"
	"github.com/nexuscrm/kernel/pkg/formula"
	"github.com/pingcap-inc/nexuscrm-controlplane/packs"
)

// MissingPhysicalColumnDetectOnlyError is returned by
// repairActiveInstalledPackSourceHashDrift when the live-state oracle reports
// that one or more physical columns registered in _System_Field are absent from
// INFORMATION_SCHEMA.COLUMNS. The reapply is not attempted; the caller must
// remediate the missing physical schema before reconciliation can proceed.
type MissingPhysicalColumnDetectOnlyError struct {
	PackName       string
	MissingColumns []string
}

func (e *MissingPhysicalColumnDetectOnlyError) Error() string {
	return fmt.Sprintf("pack %s: %d physical column(s) registered in _System_Field are missing from the database: %s — reapply blocked; drop-and-recreate or restore the missing column(s) before re-running reconcile",
		e.PackName, len(e.MissingColumns), strings.Join(e.MissingColumns, ", "))
}

// HashDriftError indicates the stored InstalledPack hashes do not match the
// current resolved pack content, despite the source identity (SourceKind +
// SourceURI) being intact. Callers may treat this as a repairable signal:
// reapply the resolved pack to update the stored hashes, then re-verify.
//
// See reconcileActiveInstalledPackSourceState for the
// "source-identity match + content-hash drift → reapply-before-strict-verify"
// substrate primitive this error supports (added in PR #559, NX#492).
type HashDriftError struct {
	PackName    string
	StoredHash  string
	CurrentHash string
}

func (e *HashDriftError) Error() string {
	return fmt.Sprintf("pack %s: stored payload hash %s differs from current %s (source identity intact)", e.PackName, e.StoredHash, e.CurrentHash)
}

// HostRequirementsDigestDriftError indicates that manifest hostRequirements
// changed without a component payload hash change. Source convergence may
// reapply the resolved source-backed pack to refresh the persisted digest and
// re-run host primitive validation against the active distribution.
type HostRequirementsDigestDriftError struct {
	PackName      string
	StoredDigest  string
	CurrentDigest string
	MissingStored bool
}

func (e *HostRequirementsDigestDriftError) Error() string {
	if e.MissingStored {
		return fmt.Sprintf("pack %s: stored host requirements digest is empty, current %s (source identity intact)", e.PackName, e.CurrentDigest)
	}
	return fmt.Sprintf("pack %s: stored host requirements digest %s differs from current %s (source identity intact)", e.PackName, e.StoredDigest, e.CurrentDigest)
}

// Verify performs readiness verification. It checks:
//  1. Schema registry health (hardened — returns real errors)
//  2. L0 substrate assertions
//  3. Runtime-required pack verification
//  4. Installed app-pack verification from _System_InstalledPack
//
// This is the POD-READINESS contract: pass `DeepChecks: false` so packs
// run only the metadata-only gates that are safe on every restart and
// tolerant of concurrent writers. For the operator-initiated
// `cmd/setup verify` path use VerifyDeep instead.
//
// If any check fails, the error describes what is wrong and the operator
// should run "cmd/setup apply" to fix it.
func Verify(ctx context.Context, db *database.TiDBConnection) error {
	return VerifyWithRunnerOptions(ctx, db, RunnerOptions{})
}

// VerifyWithRunnerOptions performs readiness verification with the immutable
// distribution options used by every source-backed pack read in this run.
func VerifyWithRunnerOptions(ctx context.Context, db *database.TiDBConnection, options RunnerOptions) error {
	return verifyImpl(ctx, db, false, options)
}

// VerifyDeep is the operator-initiated verify (`cmd/setup verify`). It
// runs the full gate set, including live-data scans that assume
// writers are quiesced. Do NOT call from the pod-readiness path —
// live-data scans scale with table size and can oscillate under
// concurrent writers.
func VerifyDeep(ctx context.Context, db *database.TiDBConnection) error {
	return VerifyDeepWithRunnerOptions(ctx, db, RunnerOptions{})
}

// VerifyDeepWithRunnerOptions performs operator verification with the same
// immutable distribution options as readiness verification.
func VerifyDeepWithRunnerOptions(ctx context.Context, db *database.TiDBConnection, options RunnerOptions) error {
	return verifyImpl(ctx, db, true, options)
}

// VerifyProfile is a narrow operator-facing profile convergence signal.
// It intentionally skips unrelated pack/assertion noise and checks only the
// schema prerequisites plus profile permission coverage drift.
func VerifyProfile(ctx context.Context, db *database.TiDBConnection) error {
	if err := schemaProvisioner.VerifySchemaReadiness(db); err != nil {
		return fmt.Errorf("schema readiness: %w", err)
	}
	if err := VerifyProfilePermissionCoverage(db.DB()); err != nil {
		return fmt.Errorf("profile permission coverage check: %w", err)
	}
	return nil
}

func verifyImpl(ctx context.Context, db *database.TiDBConnection, deep bool, options RunnerOptions) error {
	log.Printf("═══ Verify: deep=%t ═══", deep)
	verifyStart := time.Now()

	// Schema registry + critical columns
	phaseStart := time.Now()
	log.Printf("   ▶ verify phase start: schema readiness")
	if err := schemaProvisioner.VerifySchemaReadiness(db); err != nil {
		return fmt.Errorf("schema readiness: %w", err)
	}
	log.Printf("   ✅ verify phase done: schema readiness (%.1fs)", time.Since(phaseStart).Seconds())

	if deep {
		phaseStart = time.Now()
		log.Printf("   ▶ verify phase start: connected delivery schema")
		if connectedDeliverySchemaVerifier == nil {
			return fmt.Errorf("connected delivery schema verifier is not wired")
		}
		if err := connectedDeliverySchemaVerifier.VerifyConnectedDeliverySchema(db); err != nil {
			return fmt.Errorf("connected delivery schema: %w", err)
		}
		log.Printf("   ✅ verify phase done: connected delivery schema (%.1fs)", time.Since(phaseStart).Seconds())
	}

	// L0 substrate assertions. Pack-authored managed metadata drift is verified
	// by the owning pack below; global assertions must not read app-pack specs.
	phaseStart = time.Now()
	log.Printf("   ▶ verify phase start: assertions")
	if deep {
		if _, err := schemaProvisioner.RunAssertions(db, true); err != nil {
			return fmt.Errorf("assertions: %w", err)
		}
	} else {
		if podReadinessAssertionRunner == nil {
			return fmt.Errorf("pod readiness assertion runner is not wired")
		}
		if err := podReadinessAssertionRunner.RunPodReadinessAssertions(db); err != nil {
			return fmt.Errorf("assertions: %w", err)
		}
	}
	log.Printf("   ✅ verify phase done: assertions (%.1fs)", time.Since(phaseStart).Seconds())

	// Per-pack verification (runtime-required only)
	for _, p := range packs.RuntimeRequiredPacks() {
		packStart := time.Now()
		log.Printf("   ▶ runtime-required pack verify start: %s", p.Name())
		if err := p.Verify(ctx, packs.VerifyDeps{DB: db.DB(), DeepChecks: deep}); err != nil {
			return fmt.Errorf("pack %s verify: %w", p.Name(), err)
		}
		log.Printf("   ✅ runtime-required pack verify done: %s (%.1fs)", p.Name(), time.Since(packStart).Seconds())
	}

	// Report-only drift between the platform-core slice of
	// standard_objects_spec.json and live _System_Field. Non-fatal by design —
	// apply does NOT auto-heal.
	//
	// Platform Verify is tenant-agnostic by contract: tenant drift is
	// the tenant overlay's concern and runs as part of
	// controlplane.ApplyTenant (which is itself opt-in via
	// NEXUSCRM_APPLY_TENANT_OVERLAY). Calling a tenant drift check
	// unconditionally here would regress greenfield boots, where tenant
	// spec fields are absent by design.
	phaseStart = time.Now()
	log.Printf("   ▶ verify phase start: standard-field drift check")
	if err := VerifyStandardFieldSpecDrift(db.DB()); err != nil {
		return fmt.Errorf("standard-field drift check: %w", err)
	}
	log.Printf("   ✅ verify phase done: standard-field drift check (%.1fs)", time.Since(phaseStart).Seconds())

	// Installed app-pack verify is a runtime-metadata consumer. Rebuild
	// process-local declarations before verifying pack-owned live projections so
	// readiness does not depend on the L0-only bootstrap classifier installed at
	// process start. Pod-readiness consumes persisted declarations; deep/operator
	// verify still resolves source-backed packs.
	phaseStart = time.Now()
	log.Printf("   ▶ verify phase start: register installed runtime metadata")
	if err := registerInstalledRuntimeMetadataForVerify(ctx, db.DB(), deep, options.ComponentKindRegistrationSet); err != nil {
		return fmt.Errorf("installed runtime metadata: %w", err)
	}
	log.Printf("   ✅ verify phase done: register installed runtime metadata (%.1fs)", time.Since(phaseStart).Seconds())

	phaseStart = time.Now()
	log.Printf("   ▶ verify phase start: build verify runner")
	runner, err := NewRunnerWithOptions(db, options)
	if err != nil {
		return fmt.Errorf("build verify runner: %w", err)
	}
	if err := runner.RefreshCache(ctx); err != nil {
		return fmt.Errorf("refresh verify metadata cache: %w", err)
	}
	log.Printf("   ✅ verify phase done: build verify runner (%.1fs)", time.Since(phaseStart).Seconds())

	phaseStart = time.Now()
	log.Printf("   ▶ verify phase start: installed app packs")
	if err := verifyInstalledAppPacks(ctx, db, runner, deep); err != nil {
		return err
	}
	log.Printf("   ✅ verify phase done: installed app packs (%.1fs)", time.Since(phaseStart).Seconds())

	// Fail closed when ANY active formula view (pack-owned, admin __c, or L2
	// tenant) is missing/unreadable. The per-pack derived-artifact check above
	// only probes source-backed-pack-owned views; this global probe covers the
	// rest. It is the start-only readiness backstop for apply's RebuildAllViews
	// (which heals every such view on apply): without it, a restart that does not
	// run apply (#1590) would pass readiness with a broken non-pack view and then
	// fail at query time (MySQL 1356 for a dangling DEFINER). Runs in both shallow
	// (pod-readiness) and deep paths. Read-only.
	phaseStart = time.Now()
	log.Printf("   ▶ verify phase start: active formula view usability")
	if err := verifyActiveFormulaViewsUsable(ctx, db.DB()); err != nil {
		return fmt.Errorf("active formula view usability: %w", err)
	}
	log.Printf("   ✅ verify phase done: active formula view usability (%.1fs)", time.Since(phaseStart).Seconds())

	// Fatal: orphan field rows (ObjectId pointing to non-existent object)
	// are strictly broken state. Startup verify fails closed here; the
	// explicit setup apply path reconciles via
	// bootstrap.ReconcileOrphanFieldObjectIds.
	phaseStart = time.Now()
	log.Printf("   ▶ verify phase start: orphan field check")
	if err := VerifyOrphanSystemFields(db.DB()); err != nil {
		return fmt.Errorf("orphan field check: %w", err)
	}
	log.Printf("   ✅ verify phase done: orphan field check (%.1fs)", time.Since(phaseStart).Seconds())

	phaseStart = time.Now()
	log.Printf("   ▶ verify phase start: flow data integrity")
	if err := VerifyFlowDataIntegrity(ctx, db); err != nil {
		return err
	}
	log.Printf("   ✅ verify phase done: flow data integrity (%.1fs)", time.Since(phaseStart).Seconds())
	phaseStart = time.Now()
	log.Printf("   ▶ verify phase start: trigger subscription consistency")
	if err := VerifyTriggerSubscriptionConsistency(ctx, db); err != nil {
		return err
	}
	log.Printf("   ✅ verify phase done: trigger subscription consistency (%.1fs)", time.Since(phaseStart).Seconds())
	phaseStart = time.Now()
	log.Printf("   ▶ verify phase start: insert-rules verify")
	if err := runInsertRulesValidators(ctx, runner); err != nil {
		return fmt.Errorf("insert-rules verify: %w", err)
	}
	log.Printf("   ✅ verify phase done: insert-rules verify (%.1fs)", time.Since(phaseStart).Seconds())

	if deep {
		phaseStart = time.Now()
		log.Printf("   ▶ verify phase start: action table consistency")
		if err := VerifyActionTableConsistency(ctx, db.DB()); err != nil {
			return fmt.Errorf("action table consistency check: %w", err)
		}
		log.Printf("   ✅ verify phase done: action table consistency (%.1fs)", time.Since(phaseStart).Seconds())
	}

	// Deep/operator-only: fail if profile-owned PermissionSets are missing the
	// business-object ObjectPermissions / FieldPermissions coverage implied by
	// current metadata. This is intentionally not part of pod-readiness because
	// the recommended recovery action may be `setup repair`.
	if deep {
		phaseStart = time.Now()
		log.Printf("   ▶ verify phase start: profile permission coverage")
		if err := VerifyProfilePermissionCoverage(db.DB()); err != nil {
			return fmt.Errorf("profile permission coverage check: %w", err)
		}
		log.Printf("   ✅ verify phase done: profile permission coverage (%.1fs)", time.Since(phaseStart).Seconds())
	}

	log.Printf("═══ Verify completed: deep=%t (%.1fs) ═══", deep, time.Since(verifyStart).Seconds())
	return nil
}

func registerInstalledRuntimeMetadataForVerify(ctx context.Context, db *sql.DB, deep bool, registrations packs.ComponentKindRegistrationSet) error {
	if deep {
		return packs.RegisterInstalledRuntimeMetadataWithComponentKindRegistrationSet(ctx, db, registrations)
	}
	return packs.RegisterPersistedInstalledRuntimeMetadata(ctx, db)
}

func verifyInstalledAppPacks(ctx context.Context, db *database.TiDBConnection, runner *Runner, deep bool) error {
	rows, err := listInstalledSourceBackedPacks(ctx, db.DB())
	if err != nil {
		return err
	}
	formulaFunctionBundlesForValidation := &activeFormulaFunctionBundlesValidationContext{}
	for _, row := range rows {
		packStart := time.Now()
		log.Printf("   ▶ installed source-backed pack verify start: %s category=%s deep=%t", row.Name, row.Category, deep)
		if row.Status != installedPackStatusActive {
			return fmt.Errorf("installed source-backed pack %s status is %q; run setup apply-packs to complete or repair the install", row.Name, row.Status)
		}
		registrations := runner.componentKindRegistrationSet()
		if err := validatePackManagedApprovalOwnership(ctx, db.DB(), row.Name, packs.Category(strings.TrimSpace(row.Category)), registrations); err != nil {
			return fmt.Errorf("installed source-backed pack %s managed approval coverage: %w", row.Name, err)
		}
		pack, err := installedAppPackForRowWithLazyFormulaBundles(ctx, db.DB(), row, formulaFunctionBundlesForValidation, registrations)
		if err != nil {
			return err
		}
		if string(pack.Category()) != row.Category {
			return fmt.Errorf("installed source-backed pack %s category drift: registry=%q binary=%q", row.Name, row.Category, pack.Category())
		}
		if err := verifyInstalledPackHashState(row, pack); err != nil {
			return err
		}
		if err := verifyInstalledPackHostRequirementsState(row, pack); err != nil {
			return err
		}
		if !deep && row.HasRuntimeStartupDecl {
			if err := packs.VerifyInstalledPersistedRuntimeShallowByNameWithComponentKindRegistrationSet(
				ctx, db.DB(), row.Name, packs.Category(strings.TrimSpace(row.Category)), registrations,
			); err != nil {
				return fmt.Errorf("installed source-backed pack %s startup shallow declaration drift: %w", row.Name, err)
			}
			log.Printf("   ✅ installed source-backed pack startup-shallow declaration check done: %s (%.1fs)", row.Name, time.Since(packStart).Seconds())
		}
		if err := verifyInstalledPersistedRuntimeDeclarationState(row, pack, true); err != nil {
			return err
		}
		if sourceBackedPack, ok := pack.(*packs.SourceBackedPack); ok {
			if err := assertConnectorCapabilitiesHaveHandlersWithInstalledCompat(sourceBackedPack, true); err != nil {
				return fmt.Errorf("installed source-backed pack %s capability handler guard: %w", pack.Name(), err)
			}
			if err := verifyInstalledConnectorPackHandlerConfigs(ctx, db.DB(), sourceBackedPack); err != nil {
				return fmt.Errorf("installed source-backed pack %s live connector inbound handlers: %w", pack.Name(), err)
			}
		}
		if err := pack.Verify(ctx, packs.VerifyDeps{DB: db.DB(), DeepChecks: deep}); err != nil {
			return fmt.Errorf("installed source-backed pack %s verify: %w", pack.Name(), err)
		}
		if sourceBackedPack, ok := pack.(*packs.SourceBackedPack); ok {
			if err := verifySourceBackedDerivedArtifactsWithCompileProof(ctx, db.DB(), sourceBackedPack, formulaViewCompileProofForRunner(runner)); err != nil {
				return fmt.Errorf("installed source-backed pack %s derived artifacts: %w", pack.Name(), err)
			}
		}
		log.Printf("   ✅ installed source-backed pack verify done: %s (%.1fs)", row.Name, time.Since(packStart).Seconds())
	}
	return nil
}

func installedAppPackForRowWithLazyFormulaBundles(ctx context.Context, db *sql.DB, row installedPackRow, formulaFunctionBundlesForValidation *activeFormulaFunctionBundlesValidationContext, registrations packs.ComponentKindRegistrationSet) (packs.Pack, error) {
	pack, err := installedAppPackForRowWithComponentKindRegistrationSet(ctx, db, row, nil, false, registrations)
	if err == nil || !installedPackHasSourceIdentity(row) || !isMissingRuntimeFormulaFunctionError(err) {
		return pack, err
	}
	bundles, hasBundles, loadErr := formulaFunctionBundlesForValidation.load(ctx, db)
	if loadErr != nil {
		return nil, fmt.Errorf("installed source-backed pack runtime formula function bundles: %w", loadErr)
	}
	if !hasBundles {
		return nil, err
	}
	return installedAppPackForRowWithComponentKindRegistrationSet(ctx, db, row, bundles, true, registrations)
}

func installedAppPackForRow(ctx context.Context, db *sql.DB, row installedPackRow, formulaFunctionBundlesForValidation []formula.FunctionBundle, hasFormulaFunctionBundlesForValidation bool) (packs.Pack, error) {
	return installedAppPackForRowWithRegistrationSet(ctx, db, row, formulaFunctionBundlesForValidation, hasFormulaFunctionBundlesForValidation, nil)
}

func installedAppPackForRowWithComponentKindRegistrationSet(ctx context.Context, db *sql.DB, row installedPackRow, formulaFunctionBundlesForValidation []formula.FunctionBundle, hasFormulaFunctionBundlesForValidation bool, registrations packs.ComponentKindRegistrationSet) (packs.Pack, error) {
	return installedAppPackForRowWithRegistrationSet(ctx, db, row, formulaFunctionBundlesForValidation, hasFormulaFunctionBundlesForValidation, &registrations)
}

func installedAppPackForRowWithRegistrationSet(ctx context.Context, db *sql.DB, row installedPackRow, formulaFunctionBundlesForValidation []formula.FunctionBundle, hasFormulaFunctionBundlesForValidation bool, registrations *packs.ComponentKindRegistrationSet) (packs.Pack, error) {
	var operationSourceOptions *packs.PackSourceOptions
	if sourceOptions, ok := packs.OperationPackSourceOptions(ctx); ok {
		operationSourceOptions = &sourceOptions
	}
	pack, err := packs.ResolveInstalledPack(
		packs.InstalledPackResolverInput{
			Name:                  row.Name,
			Category:              packs.Category(strings.TrimSpace(row.Category)),
			AppliedPayloadHash:    row.AppliedPayloadHash.String,
			ManifestFormatVersion: row.ManifestFormatVersion.String,
			SourceKind:            row.SourceKind.String,
			SourceURI:             row.SourceURI.String,
			SourceRevision:        row.SourceRevision.String,
		},
		packs.InstalledPackResolverOptions{
			AllowBundledFallback:                          false,
			PackSourceOptions:                             operationSourceOptions,
			RuntimeFormulaFunctionBundlesForValidation:    formulaFunctionBundlesForValidation,
			HasRuntimeFormulaFunctionBundlesForValidation: hasFormulaFunctionBundlesForValidation,
			ObjectSchemaResolver:                          installedPackObjectSchemaResolver(ctx, db),
			ComponentKindRegistrationSet:                  registrations,
		},
	)
	if err != nil {
		return nil, fmt.Errorf("installed source-backed pack %s resolution: %w", row.Name, err)
	}
	return pack, nil
}

func verifyInstalledPackHashState(row installedPackRow, pack packs.Pack) error {
	if !row.AppliedPayloadHash.Valid || strings.TrimSpace(row.AppliedPayloadHash.String) == "" {
		return fmt.Errorf("installed source-backed pack %s has no applied payload hash; run setup apply-packs", row.Name)
	}
	if !row.DesiredPayloadHash.Valid || strings.TrimSpace(row.DesiredPayloadHash.String) == "" {
		return fmt.Errorf("installed source-backed pack %s has no desired payload hash; run setup apply-packs", row.Name)
	}
	if row.AppliedPayloadHash.String != row.DesiredPayloadHash.String {
		return fmt.Errorf("installed source-backed pack %s active hash state is inconsistent (applied=%q desired=%q); run setup apply-packs", row.Name, row.AppliedPayloadHash.String, row.DesiredPayloadHash.String)
	}
	storedHash := strings.TrimSpace(row.AppliedPayloadHash.String)
	format, err := packs.ValidateStoredPackPayloadHash(pack, storedHash)
	if err != nil {
		// If the stored hash has a valid V1 format but does not match the
		// current pack content, classify this as a HashDriftError so callers
		// with source identity can take the reapply branch instead of failing
		// closed. Shape failures (missing hash, invalid prefix, wrong format)
		// pass through as opaque errors — they are not repairable by reapply.
		if packs.IsPackPayloadHashV1(storedHash) {
			currentHash, hashErr := packs.PackPayloadHash(pack)
			if hashErr == nil && currentHash != storedHash {
				return &HashDriftError{
					PackName:    row.Name,
					StoredHash:  storedHash,
					CurrentHash: currentHash,
				}
			}
		}
		return fmt.Errorf("installed source-backed pack %s payload hash: %w; run setup apply-packs", row.Name, err)
	}
	if format == packs.PayloadHashFormatV1 {
		if err := verifyInstalledPackHashMetadata(row, pack); err != nil {
			return fmt.Errorf("installed source-backed pack %s hash metadata: %w; run setup apply-packs", row.Name, err)
		}
	}
	return nil
}

func verifyInstalledPackHostRequirementsState(row installedPackRow, pack packs.Pack) error {
	currentDigest, err := packs.PackHostRequirementsDigest(pack)
	if err != nil {
		return fmt.Errorf("installed source-backed pack %s host requirements digest: %w", row.Name, err)
	}
	storedDigest := strings.TrimSpace(row.HostRequirementsDigest.String)
	if !row.HostRequirementsDigest.Valid || storedDigest == "" {
		if len(packs.PackHostRequirements(pack)) == 0 {
			return nil
		}
		return &HostRequirementsDigestDriftError{
			PackName:      row.Name,
			CurrentDigest: currentDigest,
			MissingStored: true,
		}
	}
	if !packs.IsPackHostRequirementsDigestV1(storedDigest) {
		return fmt.Errorf("installed source-backed pack %s host requirements digest has unsupported format %q; run setup apply-packs", row.Name, storedDigest)
	}
	if storedDigest != currentDigest {
		return &HostRequirementsDigestDriftError{
			PackName:      row.Name,
			StoredDigest:  storedDigest,
			CurrentDigest: currentDigest,
		}
	}
	return nil
}

func verifyInstalledPackHashMetadata(row installedPackRow, pack packs.Pack) error {
	meta := packs.PackHashMetadataForPack(pack)
	if !row.PayloadHashAlgorithm.Valid || row.PayloadHashAlgorithm.String != meta.PayloadHashAlgorithm {
		return fmt.Errorf("PayloadHashAlgorithm=%q, want %q", row.PayloadHashAlgorithm.String, meta.PayloadHashAlgorithm)
	}
	if !row.ManifestFormatVersion.Valid || row.ManifestFormatVersion.String != meta.ManifestFormatVersion {
		return fmt.Errorf("ManifestFormatVersion=%q, want %q", row.ManifestFormatVersion.String, meta.ManifestFormatVersion)
	}
	if !row.PackVersion.Valid || row.PackVersion.String != meta.PackVersion {
		return fmt.Errorf("PackVersion=%q, want %q", row.PackVersion.String, meta.PackVersion)
	}
	if !row.PlatformCompatibility.Valid || row.PlatformCompatibility.String != meta.PlatformCompatibility {
		return fmt.Errorf("PlatformCompatibility=%q, want %q", row.PlatformCompatibility.String, meta.PlatformCompatibility)
	}
	return nil
}

func verifyInstalledPersistedRuntimeDeclarationState(row installedPackRow, pack packs.Pack, verifyStartupShallow bool) error {
	anyPresent := row.RuntimeObjectSpecDecl.Valid || row.RuntimeRelatedLists.Valid || row.RuntimeSearchScope.Valid || row.RuntimeSearchAllDefault.Valid || row.RuntimeFormulaBundles.Valid || row.RuntimeStartupShallow.Valid
	allPresent := row.RuntimeObjectSpecDecl.Valid && row.RuntimeRelatedLists.Valid && row.RuntimeSearchScope.Valid && row.RuntimeSearchAllDefault.Valid
	if row.HasRuntimeFormula {
		allPresent = allPresent && row.RuntimeFormulaBundles.Valid
	}
	if verifyStartupShallow && row.HasRuntimeStartupDecl {
		allPresent = allPresent && row.RuntimeStartupShallow.Valid
	}
	if !anyPresent {
		return nil
	}
	if !allPresent {
		return fmt.Errorf("installed source-backed pack %s persisted runtime declaration is incomplete; run setup apply-packs", row.Name)
	}
	startupShallowJSON, err := startupShallowJSONForVerify(row, pack, verifyStartupShallow)
	if err != nil {
		return fmt.Errorf("installed source-backed pack %s persisted runtime declaration startup shallow source identity: %w; run setup apply-packs", row.Name, err)
	}
	formulaFunctionBundlesJSON := "[]"
	if row.HasRuntimeFormula {
		formulaFunctionBundlesJSON = row.RuntimeFormulaBundles.String
	}
	if err := packs.VerifyPersistedRuntimeDeclarationAgainstPackWithSearchAllDefaultFieldsAndFormulaFunctionBundles(
		row.Name,
		row.RuntimeObjectSpecDecl.String,
		row.RuntimeRelatedLists.String,
		row.RuntimeSearchScope.String,
		row.RuntimeSearchAllDefault.String,
		formulaFunctionBundlesJSON,
		startupShallowJSON,
		pack,
	); err != nil {
		return fmt.Errorf("installed source-backed pack %s persisted runtime declaration drifted from source identity; run setup apply-packs", row.Name)
	}
	return nil
}

func classifyCompleteInstalledPersistedRuntimeDeclarationState(row installedPackRow, pack packs.Pack) (bool, error) {
	declaration, err := completeInstalledPersistedRuntimeDeclaration(row)
	if err != nil {
		return false, err
	}
	if _, err := declaration.LoadPack(row.Name); err != nil {
		return false, fmt.Errorf("installed source-backed pack %s persisted runtime declaration is malformed: %w; run setup apply-packs", row.Name, err)
	}
	return verifyInstalledPersistedRuntimeDeclarationState(row, pack, true) == nil, nil
}

func completeInstalledPersistedRuntimeDeclaration(row installedPackRow) (packs.PersistedRuntimeDeclarationJSON, error) {
	if !row.RuntimeObjectSpecDecl.Valid || !row.RuntimeRelatedLists.Valid ||
		!row.RuntimeSearchScope.Valid || !row.RuntimeSearchAllDefault.Valid ||
		!row.RuntimeFormulaBundles.Valid || !row.RuntimeStartupShallow.Valid {
		return packs.PersistedRuntimeDeclarationJSON{}, fmt.Errorf("installed source-backed pack %s persisted runtime declaration is incomplete; run setup apply-packs", row.Name)
	}
	return packs.PersistedRuntimeDeclarationJSON{
		ObjectSpecs:            row.RuntimeObjectSpecDecl.String,
		RelatedLists:           row.RuntimeRelatedLists.String,
		SearchScope:            row.RuntimeSearchScope.String,
		SearchAllDefaultFields: row.RuntimeSearchAllDefault.String,
		FormulaFunctionBundles: row.RuntimeFormulaBundles.String,
		StartupShallow:         row.RuntimeStartupShallow.String,
	}, nil
}

func startupShallowJSONForVerify(row installedPackRow, pack packs.Pack, verifyStartupShallow bool) (string, error) {
	if !verifyStartupShallow {
		return "", nil
	}
	if !row.HasRuntimeStartupDecl {
		expected, err := packs.BuildPersistedRuntimeDeclaration(pack)
		if err != nil {
			return "", fmt.Errorf("build persisted runtime declaration: %w", err)
		}
		return expected.StartupShallowDeclaredRowsJSON(), nil
	}
	return row.RuntimeStartupShallow.String, nil
}
