package setupcmd

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"strings"

	"github.com/nexuscrm/automation"
	"github.com/nexuscrm/automation/standardaction"
	"github.com/nexuscrm/backend/internal/bootstrap"
	database "github.com/nexuscrm/db"
	metadatapkg "github.com/nexuscrm/metadata"
	"github.com/nexuscrm/metadata/programchecker"
	permissionpkg "github.com/nexuscrm/permissions"
	"github.com/nexuscrm/shared/pkg/constants"
	sharedcontract "github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/connector"
	"github.com/pingcap-inc/nexuscrm-controlplane"
	"github.com/pingcap-inc/nexuscrm-controlplane/packs"
)

func verifyInstalledPackMetadataProgramsWithComponentKindRegistrationSet(ctx context.Context, db *database.TiDBConnection, registrationSet packs.ComponentKindRegistrationSet, ownerPacks ...string) error {
	runner, err := controlplane.NewRunnerWithOptions(db, controlplane.RunnerOptions{ComponentKindRegistrationSet: registrationSet})
	if err != nil {
		return fmt.Errorf("build metadata program check runner: %w", err)
	}
	return verifyRunnerMetadataProgramsWithComponentKindRegistrationSet(ctx, runner, registrationSet, ownerPacks...)
}

func verifyCandidateMetadataProgramsWithComponentKindRegistrationSet(ctx context.Context, runner *controlplane.Runner, registrationSet packs.ComponentKindRegistrationSet, candidates []packs.Pack) error {
	ownerPacks := candidatePackNames(candidates)
	if len(ownerPacks) == 0 {
		return fmt.Errorf("metadata program candidate pack set is unavailable")
	}
	err := verifyPreparedRunnerMetadataProgramsWithOptions(ctx, runner, setupMetadataProgramCheckOptions{
		policyTargets:                setupPolicyTargetsFromCandidatePacks,
		componentKindRegistrationSet: &registrationSet,
		candidatePacks:               candidates,
	}, ownerPacks...)
	return classifyCandidateMetadataProgramVerification(err)
}

func classifyCandidateMetadataProgramVerification(err error) error {
	if err == nil {
		return nil
	}
	var diagnostics metadataProgramDiagnosticsError
	if errors.As(err, &diagnostics) {
		if diagnostics.diagnostics.HasReaderErrors() {
			return err
		}
		return controlplane.MarkOfflineCandidateLiveStateDrift(err)
	}
	return err
}

func verifyRunnerMetadataProgramsWithComponentKindRegistrationSet(ctx context.Context, runner *controlplane.Runner, registrationSet packs.ComponentKindRegistrationSet, ownerPacks ...string) error {
	return verifyRunnerMetadataProgramsWithOptions(ctx, runner, setupMetadataProgramCheckOptions{
		registerRuntimeMetadata: func(ctx context.Context, db *sql.DB) error {
			return packs.RegisterInstalledRuntimeMetadataWithComponentKindRegistrationSet(ctx, db, registrationSet)
		},
		policyTargets:                setupPolicyTargetsFromInstalledSource,
		componentKindRegistrationSet: &registrationSet,
	}, ownerPacks...)
}

func verifyInstalledPackPersistedMetadataPrograms(ctx context.Context, db *database.TiDBConnection, ownerPacks ...string) error {
	runner, err := controlplane.NewRunner(db)
	if err != nil {
		return fmt.Errorf("build metadata program check runner: %w", err)
	}
	return verifyRunnerMetadataProgramsWithOptions(ctx, runner, setupMetadataProgramCheckOptions{
		registerRuntimeMetadata: packs.RegisterPersistedInstalledRuntimeMetadata,
		policyTargets:           setupPolicyTargetsFromPersistedRuntime,
	}, ownerPacks...)
}

type setupPolicyTargetSource int

const (
	setupPolicyTargetsFromInstalledSource setupPolicyTargetSource = iota
	setupPolicyTargetsFromPersistedRuntime
	setupPolicyTargetsFromCandidatePacks
)

type setupMetadataProgramCheckOptions struct {
	registerRuntimeMetadata      func(context.Context, *sql.DB) error
	policyTargets                setupPolicyTargetSource
	componentKindRegistrationSet *packs.ComponentKindRegistrationSet
	candidatePacks               []packs.Pack
}

func verifyRunnerMetadataProgramsWithOptions(ctx context.Context, runner *controlplane.Runner, opts setupMetadataProgramCheckOptions, ownerPacks ...string) error {
	if runner == nil || runner.DB == nil || runner.SvcMgr == nil {
		return fmt.Errorf("metadata program check runner is unavailable")
	}
	if opts.registerRuntimeMetadata == nil {
		return fmt.Errorf("metadata program check runtime metadata registrar is unavailable")
	}
	rawDB := runner.DB.DB()
	if err := opts.registerRuntimeMetadata(ctx, rawDB); err != nil {
		return fmt.Errorf("register installed runtime metadata for metadata program check: %w", err)
	}
	return verifyPreparedRunnerMetadataProgramsWithOptions(ctx, runner, opts, ownerPacks...)
}

func verifyPreparedRunnerMetadataProgramsWithOptions(ctx context.Context, runner *controlplane.Runner, opts setupMetadataProgramCheckOptions, ownerPacks ...string) error {
	if runner == nil || runner.DB == nil || runner.SvcMgr == nil {
		return fmt.Errorf("metadata program check runner is unavailable")
	}
	rawDB := runner.DB.DB()
	if err := runner.RefreshCache(ctx); err != nil {
		return fmt.Errorf("refresh metadata cache for metadata program check: %w", err)
	}
	metaSvc := runner.SvcMgr.Metadata()
	if metaSvc == nil {
		return fmt.Errorf("metadata service is unavailable for metadata program check")
	}
	ownerPacks = normalizeMetadataProgramOwnerPacks(ownerPacks)
	if len(ownerPacks) == 0 {
		activeAppPacks, err := packs.ActiveInstalledAppPacks(ctx, rawDB)
		if err != nil {
			return fmt.Errorf("resolve active app packs for metadata program check: %w", err)
		}
		ownerPacks = activeInstalledAppPackNames(activeAppPacks)
		if len(ownerPacks) == 0 {
			return nil
		}
	}
	actions, err := setupActionSignatureReader(ctx, rawDB, metaSvc)
	if err != nil {
		return err
	}
	policyMetadata, err := setupPolicyCheckMetadataReader(ctx, rawDB, metaSvc, ownerPacks, opts.policyTargets, opts.componentKindRegistrationSet, opts.candidatePacks)
	if err != nil {
		return err
	}
	diags := programchecker.CheckObjectAccessPolicyReferences(ctx, programchecker.ObjectAccessPolicyReferenceCheckOptions{
		Metadata:     policyMetadata,
		Policy:       setupPolicyReader(runner.SvcMgr.Permissions()),
		Capabilities: setupCapabilityCatalogReader(runner.SvcMgr.Permissions()),
		Validation:   metadatapkg.NewProgramValidationRuleReader(metaSvc),
	})
	flowDiags, err := metaSvc.CheckInstalledPackFlowPrograms(ctx, metadatapkg.InstalledPackFlowProgramCheckOptions{
		OwnerPacks:            ownerPacks,
		Actions:               actions,
		Connectors:            setupConnectorMappingReader(rawDB),
		NormalizeFlowMetadata: setupNormalizeFlowMetadataForProgramCheck,
	})
	if err != nil {
		return err
	}
	diags = append(diags, flowDiags...)
	if diags.HasErrors() {
		return metadataProgramDiagnosticsError{diagnostics: diags.ErrorDiagnostics()}
	}
	return nil
}

func setupConnectorMappingReader(rawDB *sql.DB) programchecker.ConnectorMappingReader {
	return programchecker.ConnectorMappingReaderFunc(func(ctx context.Context, q connector.ResolveQuery) ([]connector.ConnectorFieldMappingInput, error) {
		return bootstrap.ResolvePackConnectorFieldMapping(ctx, rawDB, q)
	})
}

func setupPolicyReader(permissionSvc *permissionpkg.PermissionService) programchecker.PolicyReader {
	if permissionSvc == nil {
		return nil
	}
	return programchecker.CapabilityPolicyAdapter{Policy: permissionpkg.NewContractPolicy(permissionSvc)}
}

func setupPolicyCheckMetadataReader(ctx context.Context, rawDB *sql.DB, metaSvc *metadatapkg.MetadataService, ownerPacks []string, targetSource setupPolicyTargetSource, registrationSet *packs.ComponentKindRegistrationSet, candidatePacks []packs.Pack) (programchecker.ProgramMetadataReader, error) {
	base := metadatapkg.NewProgramMetadataReader(metaSvc)
	targets, err := setupPolicyCheckTargetObjects(ctx, rawDB, metaSvc, ownerPacks, targetSource, registrationSet, candidatePacks)
	if err != nil {
		return nil, err
	}
	return setupPolicyScopedMetadataReader{
		ProgramMetadataReader: base,
		targets:               targets,
	}, nil
}

type setupPolicyScopedMetadataReader struct {
	programchecker.ProgramMetadataReader
	targets setupPolicyCheckTargets
}

func (r setupPolicyScopedMetadataReader) Schemas(ctx context.Context) ([]*models.ObjectMetadata, error) {
	if len(r.targets) == 0 {
		return nil, nil
	}
	schemas, err := r.ProgramMetadataReader.Schemas(ctx)
	if err != nil {
		return nil, err
	}
	out := make([]*models.ObjectMetadata, 0, len(schemas))
	for _, schema := range schemas {
		if schema == nil {
			continue
		}
		target, ok := r.targets[setupPolicyCheckTargetKey(schema.APIName)]
		if !ok {
			continue
		}
		projected := setupProjectPolicyCheckSchema(schema, target)
		if projected != nil {
			out = append(out, projected)
		}
	}
	return out, nil
}

type setupPolicySurface uint8

const (
	setupPolicySurfaceAccessExpressions setupPolicySurface = 1 << iota
	setupPolicySurfaceRowRead
	setupPolicySurfaceRecordMutation
	setupPolicySurfaceLifecycle
	setupPolicySurfaceCacheInvalidation
	setupPolicySurfaceWriteValidation

	setupPolicySurfaceObjectSpecOwned = setupPolicySurfaceAccessExpressions |
		setupPolicySurfaceRowRead |
		setupPolicySurfaceCacheInvalidation |
		setupPolicySurfaceWriteValidation
)

type setupPolicyCheckTarget struct {
	objectAPIName string
	surfaces      setupPolicySurface
}

func (t setupPolicyCheckTarget) has(surface setupPolicySurface) bool {
	return t.surfaces&surface != 0
}

type setupPolicyCheckTargets map[string]setupPolicyCheckTarget

func setupPolicyCheckTargetObjects(ctx context.Context, rawDB *sql.DB, metaSvc *metadatapkg.MetadataService, ownerPacks []string, targetSource setupPolicyTargetSource, registrationSet *packs.ComponentKindRegistrationSet, candidatePacks []packs.Pack) (setupPolicyCheckTargets, error) {
	switch targetSource {
	case setupPolicyTargetsFromInstalledSource:
		return setupPolicyCheckTargetObjectsFromInstalledSource(ctx, rawDB, metaSvc, ownerPacks, registrationSet)
	case setupPolicyTargetsFromPersistedRuntime:
		return setupPolicyCheckTargetObjectsFromPersistedRuntime(ctx, rawDB, ownerPacks)
	case setupPolicyTargetsFromCandidatePacks:
		return setupPolicyCheckTargetObjectsFromCandidatePacks(candidatePacks, ownerPacks)
	default:
		return nil, fmt.Errorf("unknown metadata program policy target source %d", targetSource)
	}
}

func setupProjectPolicyCheckSchema(schema *models.ObjectMetadata, target setupPolicyCheckTarget) *models.ObjectMetadata {
	if schema == nil || schema.AccessPolicy == nil || target.surfaces == 0 {
		return nil
	}
	if schema.IsCustomized {
		target.surfaces &^= setupPolicySurfaceObjectSpecOwned
		if target.surfaces == 0 {
			return nil
		}
	}
	policy := &models.ObjectAccessPolicy{}
	if target.has(setupPolicySurfaceAccessExpressions) {
		policy.Mode = schema.AccessPolicy.Mode
		policy.ObjectRead = schema.AccessPolicy.ObjectRead
		policy.ObjectCreate = schema.AccessPolicy.ObjectCreate
		policy.ObjectEdit = schema.AccessPolicy.ObjectEdit
		policy.ObjectDelete = schema.AccessPolicy.ObjectDelete
		policy.FieldRead = schema.AccessPolicy.FieldRead
		policy.FieldEdit = schema.AccessPolicy.FieldEdit
	}
	if target.has(setupPolicySurfaceRowRead) {
		policy.RowRead = schema.AccessPolicy.RowRead
	}
	if target.has(setupPolicySurfaceRecordMutation) {
		policy.RecordMutation = schema.AccessPolicy.RecordMutation
	}
	if target.has(setupPolicySurfaceLifecycle) {
		policy.Lifecycle = schema.AccessPolicy.Lifecycle
	}
	if target.has(setupPolicySurfaceCacheInvalidation) {
		policy.CacheInvalidation = schema.AccessPolicy.CacheInvalidation
	}
	if target.has(setupPolicySurfaceWriteValidation) {
		policy.WriteValidation = schema.AccessPolicy.WriteValidation
	}
	if setupProjectedAccessPolicyEmpty(policy) {
		return nil
	}
	out := *schema
	out.AccessPolicy = policy
	return &out
}

func setupProjectedAccessPolicyEmpty(policy *models.ObjectAccessPolicy) bool {
	return policy == nil ||
		(policy.ObjectRead == nil &&
			policy.ObjectCreate == nil &&
			policy.ObjectEdit == nil &&
			policy.ObjectDelete == nil &&
			policy.FieldRead == nil &&
			policy.FieldEdit == nil &&
			policy.RowRead == nil &&
			policy.RecordMutation == nil &&
			policy.Lifecycle == nil &&
			policy.CacheInvalidation == nil &&
			policy.WriteValidation == nil)
}

type setupInstalledPackRow struct {
	name                  string
	category              string
	manifestFormatVersion string
	sourceKind            string
	sourceURI             string
	sourceRevision        string
	appliedPayloadHash    string
}

func setupPolicyCheckTargetObjectsFromInstalledSource(ctx context.Context, rawDB *sql.DB, metaSvc *metadatapkg.MetadataService, ownerPacks []string, registrationSet *packs.ComponentKindRegistrationSet) (setupPolicyCheckTargets, error) {
	targets := setupPolicyCheckTargets{}
	rows, err := setupInstalledPackRows(ctx, rawDB, ownerPacks)
	if err != nil {
		return nil, err
	}
	for _, row := range rows {
		pack, err := packs.ResolveInstalledPack(packs.InstalledPackResolverInput{
			Name:                  row.name,
			Category:              packs.Category(row.category),
			AppliedPayloadHash:    row.appliedPayloadHash,
			ManifestFormatVersion: row.manifestFormatVersion,
			SourceKind:            row.sourceKind,
			SourceURI:             row.sourceURI,
			SourceRevision:        row.sourceRevision,
		}, packs.InstalledPackResolverOptions{
			ComponentKindRegistrationSet: registrationSet,
			ObjectSchemaResolver: func(apiName string) *models.ObjectMetadata {
				if metaSvc == nil {
					return nil
				}
				return metaSvc.GetSchema(ctx, apiName)
			},
		})
		if err != nil {
			return nil, fmt.Errorf("resolve installed pack %s for metadata program policy check: %w", row.name, err)
		}
		composable, ok := pack.(packs.ComposablePack)
		if !ok {
			continue
		}
		if err := addSetupPolicyCheckTargetsFromComponents(targets, composable.Components()); err != nil {
			return nil, fmt.Errorf("resolve policy check target objects for pack %s: %w", row.name, err)
		}
	}
	return targets, nil
}

func candidatePackNames(candidatePacks []packs.Pack) []string {
	names := make([]string, 0, len(candidatePacks))
	for _, pack := range candidatePacks {
		if pack == nil {
			continue
		}
		if name := strings.TrimSpace(pack.Name()); name != "" {
			names = append(names, name)
		}
	}
	return names
}

func setupPolicyCheckTargetObjectsFromCandidatePacks(candidatePacks []packs.Pack, ownerPacks []string) (setupPolicyCheckTargets, error) {
	targets := setupPolicyCheckTargets{}
	filter := setupOwnerPackFilter(ownerPacks)
	seen := make(map[string]struct{}, len(candidatePacks))
	for _, pack := range candidatePacks {
		if pack == nil {
			continue
		}
		name := strings.TrimSpace(pack.Name())
		key := strings.ToLower(name)
		if key == "" {
			continue
		}
		if _, duplicate := seen[key]; duplicate {
			return nil, fmt.Errorf("candidate pack %s is duplicated for metadata program policy check", name)
		}
		seen[key] = struct{}{}
		if _, ok := filter[key]; !ok {
			continue
		}
		composable, ok := pack.(packs.ComposablePack)
		if !ok {
			continue
		}
		if err := addSetupPolicyCheckTargetsFromComponents(targets, composable.Components()); err != nil {
			return nil, fmt.Errorf("resolve candidate policy check target objects for pack %s: %w", name, err)
		}
	}
	for _, ownerPack := range ownerPacks {
		key := strings.ToLower(strings.TrimSpace(ownerPack))
		if key == "" {
			continue
		}
		if _, ok := seen[key]; !ok {
			return nil, fmt.Errorf("candidate pack %s is unavailable for metadata program policy check", ownerPack)
		}
	}
	return targets, nil
}

type setupRuntimeObjectSpecPayloadProvider interface {
	RuntimeObjectSpecPayloads() [][]byte
}

func setupPolicyCheckTargetObjectsFromPersistedRuntime(ctx context.Context, rawDB *sql.DB, ownerPacks []string) (setupPolicyCheckTargets, error) {
	if rawDB == nil {
		return nil, fmt.Errorf("database is unavailable for metadata program policy check")
	}
	ownerPacks = normalizeMetadataProgramOwnerPacks(ownerPacks)
	if len(ownerPacks) == 0 {
		return nil, nil
	}
	runtimePacks, err := packs.LoadActiveInstalledPersistedRuntimeDeclarations(ctx, rawDB)
	if err != nil {
		return nil, fmt.Errorf("load persisted runtime declarations for metadata program policy check: %w", err)
	}
	targets, err := setupPolicyCheckTargetObjectsFromRuntimePacks(runtimePacks, ownerPacks)
	if err != nil {
		return nil, err
	}
	if err := addSetupPolicyCheckTargetsFromPersistedSidecars(ctx, rawDB, targets, ownerPacks); err != nil {
		return nil, err
	}
	return targets, nil
}

func setupPolicyCheckTargetObjectsFromRuntimePacks(runtimePacks []packs.Pack, ownerPacks []string) (setupPolicyCheckTargets, error) {
	targets := setupPolicyCheckTargets{}
	filter := setupOwnerPackFilter(ownerPacks)
	if len(filter) == 0 {
		return targets, nil
	}
	seen := map[string]struct{}{}
	for _, pack := range runtimePacks {
		if pack == nil {
			continue
		}
		packName := strings.TrimSpace(pack.Name())
		if _, ok := filter[strings.ToLower(packName)]; !ok {
			continue
		}
		seen[strings.ToLower(packName)] = struct{}{}
		provider, ok := pack.(setupRuntimeObjectSpecPayloadProvider)
		if !ok {
			continue
		}
		for _, payload := range provider.RuntimeObjectSpecPayloads() {
			if err := addSetupPolicyCheckTargetsFromPayload(targets, packs.ComponentKindObjectSpec, "", payload); err != nil {
				return nil, fmt.Errorf("resolve persisted policy check target objects for pack %s: %w", packName, err)
			}
		}
	}
	for _, ownerPack := range ownerPacks {
		key := strings.ToLower(strings.TrimSpace(ownerPack))
		if key == "" {
			continue
		}
		if _, ok := seen[key]; !ok {
			return nil, fmt.Errorf("installed pack %s is unavailable for metadata program policy check", ownerPack)
		}
	}
	return targets, nil
}

func addSetupPolicyCheckTargetsFromPersistedSidecars(ctx context.Context, rawDB *sql.DB, targets setupPolicyCheckTargets, ownerPacks []string) error {
	if rawDB == nil {
		return fmt.Errorf("database is unavailable for metadata program policy check")
	}
	filter := setupOwnerPackFilter(ownerPacks)
	if len(filter) == 0 {
		return nil
	}
	rows, err := rawDB.QueryContext(ctx, fmt.Sprintf(
		"SELECT `%s`, COALESCE(`%s`, ''), COALESCE(`%s`, '')\n"+
			"FROM `%s`\n"+
			"WHERE COALESCE(`%s`, 0) = 0\n"+
			"ORDER BY `%s`",
		constants.FieldSysObject_APIName,
		constants.FieldSysObject_AccessPolicyRecordMutationPackName,
		constants.FieldSysObject_AccessPolicyLifecyclePackName,
		constants.TableObject,
		constants.FieldIsDeleted,
		constants.FieldSysObject_APIName,
	))
	if err != nil {
		return fmt.Errorf("query persisted access-policy sidecar owners for metadata program policy check: %w", err)
	}
	defer rows.Close()

	for rows.Next() {
		var objectAPIName, recordMutationPackName, lifecyclePackName string
		if err := rows.Scan(&objectAPIName, &recordMutationPackName, &lifecyclePackName); err != nil {
			return fmt.Errorf("scan persisted access-policy sidecar owner for metadata program policy check: %w", err)
		}
		if _, ok := filter[strings.ToLower(strings.TrimSpace(recordMutationPackName))]; ok {
			addSetupPolicyCheckTarget(targets, objectAPIName, setupPolicySurfaceRecordMutation)
		}
		if _, ok := filter[strings.ToLower(strings.TrimSpace(lifecyclePackName))]; ok {
			addSetupPolicyCheckTarget(targets, objectAPIName, setupPolicySurfaceLifecycle)
		}
	}
	if err := rows.Err(); err != nil {
		return fmt.Errorf("iterate persisted access-policy sidecar owners for metadata program policy check: %w", err)
	}
	return nil
}

func setupOwnerPackFilter(ownerPacks []string) map[string]struct{} {
	out := map[string]struct{}{}
	for _, ownerPack := range ownerPacks {
		key := strings.ToLower(strings.TrimSpace(ownerPack))
		if key != "" {
			out[key] = struct{}{}
		}
	}
	return out
}

func setupInstalledPackRows(ctx context.Context, rawDB *sql.DB, ownerPacks []string) ([]setupInstalledPackRow, error) {
	if rawDB == nil {
		return nil, fmt.Errorf("database is unavailable for metadata program policy check")
	}
	ownerPacks = normalizeMetadataProgramOwnerPacks(ownerPacks)
	if len(ownerPacks) == 0 {
		return nil, nil
	}
	placeholders := make([]string, 0, len(ownerPacks))
	args := make([]any, 0, len(ownerPacks))
	for _, ownerPack := range ownerPacks {
		placeholders = append(placeholders, "?")
		args = append(args, strings.ToLower(strings.TrimSpace(ownerPack)))
	}
	rows, err := rawDB.QueryContext(ctx, fmt.Sprintf(`
SELECT Name, Category, COALESCE(ManifestFormatVersion, ''), COALESCE(SourceKind, ''),
       COALESCE(SourceURI, ''), COALESCE(SourceRevision, ''), COALESCE(AppliedPayloadHash, '')
FROM _System_InstalledPack
WHERE Status = 'active'
  AND COALESCE(IsDeleted, 0) = 0
  AND LOWER(Name) IN (%s)
ORDER BY Name`, strings.Join(placeholders, ",")), args...)
	if err != nil {
		return nil, fmt.Errorf("query installed packs for metadata program policy check: %w", err)
	}
	defer rows.Close()

	out := make([]setupInstalledPackRow, 0, len(ownerPacks))
	seen := map[string]struct{}{}
	for rows.Next() {
		var row setupInstalledPackRow
		if err := rows.Scan(
			&row.name,
			&row.category,
			&row.manifestFormatVersion,
			&row.sourceKind,
			&row.sourceURI,
			&row.sourceRevision,
			&row.appliedPayloadHash,
		); err != nil {
			return nil, fmt.Errorf("scan installed pack for metadata program policy check: %w", err)
		}
		row.name = strings.TrimSpace(row.name)
		row.category = strings.TrimSpace(row.category)
		row.manifestFormatVersion = strings.TrimSpace(row.manifestFormatVersion)
		row.sourceKind = strings.TrimSpace(row.sourceKind)
		row.sourceURI = strings.TrimSpace(row.sourceURI)
		row.sourceRevision = strings.TrimSpace(row.sourceRevision)
		row.appliedPayloadHash = strings.TrimSpace(row.appliedPayloadHash)
		if row.name == "" {
			continue
		}
		seen[strings.ToLower(row.name)] = struct{}{}
		out = append(out, row)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate installed packs for metadata program policy check: %w", err)
	}
	for _, ownerPack := range ownerPacks {
		if _, ok := seen[strings.ToLower(strings.TrimSpace(ownerPack))]; !ok {
			return nil, fmt.Errorf("installed pack %s is unavailable for metadata program policy check", ownerPack)
		}
	}
	return out, nil
}

func addSetupPolicyCheckTargetsFromComponents(targets setupPolicyCheckTargets, components []packs.PackComponent) error {
	for _, component := range components {
		if component == nil {
			continue
		}
		payload, err := component.ExpectedPayload()
		if err != nil {
			return fmt.Errorf("%s %s payload: %w", component.Kind(), component.BusinessKey(), err)
		}
		if err := addSetupPolicyCheckTargetsFromPayload(targets, component.Kind(), component.BusinessKey(), payload); err != nil {
			return fmt.Errorf("%s %s: %w", component.Kind(), component.BusinessKey(), err)
		}
	}
	return nil
}

func addSetupPolicyCheckTargetsFromPayload(targets setupPolicyCheckTargets, kind packs.ComponentKind, businessKey string, payload []byte) error {
	if targets == nil {
		return nil
	}
	switch kind {
	case packs.ComponentKindObjectSpec:
		var specs []struct {
			APIName      string           `json:"apiName"`
			AccessPolicy *json.RawMessage `json:"accessPolicy"`
		}
		if err := json.Unmarshal(payload, &specs); err != nil {
			return fmt.Errorf("parse object_spec payload: %w", err)
		}
		for _, spec := range specs {
			if spec.AccessPolicy == nil || len(strings.TrimSpace(string(*spec.AccessPolicy))) == 0 || strings.EqualFold(strings.TrimSpace(string(*spec.AccessPolicy)), "null") {
				continue
			}
			var policy models.ObjectAccessPolicy
			if err := json.Unmarshal(*spec.AccessPolicy, &policy); err != nil {
				return fmt.Errorf("parse object_spec accessPolicy for %s: %w", spec.APIName, err)
			}
			addSetupPolicyCheckTarget(targets, spec.APIName, setupPolicySurfacesForObjectSpec(&policy))
		}
	case packs.ComponentKindObjectRecordMutationPolicy, packs.ComponentKindObjectLifecyclePolicy:
		var sidecar struct {
			ObjectAPIName string `json:"objectApiName"`
		}
		if err := json.Unmarshal(payload, &sidecar); err != nil {
			return fmt.Errorf("parse %s payload: %w", kind, err)
		}
		objectAPIName := strings.TrimSpace(sidecar.ObjectAPIName)
		if objectAPIName == "" {
			objectAPIName = businessKey
		}
		if kind == packs.ComponentKindObjectRecordMutationPolicy {
			addSetupPolicyCheckTarget(targets, objectAPIName, setupPolicySurfaceRecordMutation)
		} else {
			addSetupPolicyCheckTarget(targets, objectAPIName, setupPolicySurfaceLifecycle)
		}
	}
	return nil
}

func setupPolicySurfacesForObjectSpec(policy *models.ObjectAccessPolicy) setupPolicySurface {
	if policy == nil {
		return 0
	}
	var surfaces setupPolicySurface
	if policy.ObjectRead != nil || policy.ObjectCreate != nil || policy.ObjectEdit != nil || policy.ObjectDelete != nil || policy.FieldRead != nil || policy.FieldEdit != nil {
		surfaces |= setupPolicySurfaceAccessExpressions
	}
	if policy.RowRead != nil {
		surfaces |= setupPolicySurfaceRowRead
	}
	if policy.CacheInvalidation != nil {
		surfaces |= setupPolicySurfaceCacheInvalidation
	}
	if policy.WriteValidation != nil {
		surfaces |= setupPolicySurfaceWriteValidation
	}
	return surfaces
}

func addSetupPolicyCheckTarget(targets setupPolicyCheckTargets, objectAPIName string, surfaces setupPolicySurface) {
	objectAPIName = strings.TrimSpace(objectAPIName)
	if objectAPIName == "" || surfaces == 0 {
		return
	}
	key := setupPolicyCheckTargetKey(objectAPIName)
	target := targets[key]
	if strings.TrimSpace(target.objectAPIName) == "" {
		target.objectAPIName = objectAPIName
	}
	target.surfaces |= surfaces
	targets[key] = target
}

func setupPolicyCheckTargetKey(objectAPIName string) string {
	return strings.ToLower(strings.TrimSpace(objectAPIName))
}

type setupSystemPermissionCatalog interface {
	ListSystemPermissions(context.Context) ([]models.SystemPermission, error)
}

func setupCapabilityCatalogReader(catalog setupSystemPermissionCatalog) programchecker.CapabilityCatalogReader {
	if catalog == nil {
		return nil
	}
	var cached map[string]struct{}
	return programchecker.CapabilityCatalogReaderFunc(func(ctx context.Context, apiName string) (bool, error) {
		apiName = strings.TrimSpace(apiName)
		if apiName == "" {
			return false, nil
		}
		if cached == nil {
			rows, err := catalog.ListSystemPermissions(ctx)
			if err != nil {
				return false, err
			}
			cached = make(map[string]struct{}, len(rows))
			for _, row := range rows {
				if !row.IsActive {
					continue
				}
				name := strings.TrimSpace(row.APIName)
				if name == "" {
					continue
				}
				cached[name] = struct{}{}
			}
		}
		_, ok := cached[apiName]
		return ok, nil
	})
}

type setupStandardActionSignatureReader struct {
	actions                   *standardaction.Service
	metaSvc                   *metadatapkg.MetadataService
	registeredActionCallTypes map[string]string
}

func setupActionSignatureReader(ctx context.Context, rawDB *sql.DB, metaSvc *metadatapkg.MetadataService) (programchecker.ActionSignatureReader, error) {
	registeredActionCallTypes, err := setupRegisteredActionCallTypes(ctx, rawDB)
	if err != nil {
		return nil, err
	}
	actionSvc := standardaction.NewService(rawDB).WithRuntime(standardaction.RuntimeDeps{
		Metadata:        metaSvc,
		StandardActions: bootstrap.NewStandardActionReaderImpl(rawDB),
	})
	return setupStandardActionSignatureReader{
		actions:                   actionSvc,
		metaSvc:                   metaSvc,
		registeredActionCallTypes: registeredActionCallTypes,
	}, nil
}

func (r setupStandardActionSignatureReader) ActionSignature(ctx context.Context, actionName string) (programchecker.ActionSignature, bool, error) {
	actionName = strings.TrimSpace(actionName)
	if actionName == "" || r.actions == nil {
		return programchecker.ActionSignature{}, false, nil
	}
	if registeredName, ok := r.registeredActionCallTypes[strings.ToLower(actionName)]; ok {
		return programchecker.ActionSignature{Name: registeredName, SkipParameterValidation: true}, true, nil
	}
	resolution, err := r.actions.ResolveAction(ctx, actionName)
	if err != nil {
		return programchecker.ActionSignature{}, false, err
	}
	if resolution.State != standardaction.ActionActive || resolution.Action == nil {
		return programchecker.ActionSignature{}, false, nil
	}
	schema, err := r.actions.SchemaContract(ctx, resolution.Action.ID, r.metaSvc, nil)
	if err != nil {
		return programchecker.ActionSignature{}, false, err
	}
	return programchecker.ActionSignature{
		Name:    resolution.Action.Name,
		Inputs:  setupActionSchemaFieldsToContract(schema.Inputs),
		Outputs: setupActionSchemaFieldsToContract(schema.Outputs),
	}, true, nil
}

func setupRegisteredActionCallTypes(ctx context.Context, rawDB *sql.DB) (map[string]string, error) {
	out := map[string]string{}
	registrations := automation.DefaultAppFlowActionCallRegistrations()
	if rawDB != nil {
		descriptors, err := packs.ActiveFlowActionCallHandlerDescriptors(ctx, rawDB)
		if err != nil {
			return nil, fmt.Errorf("read active flow action-call handler descriptors for metadata program check: %w", err)
		}
		installedRegistrations, err := automation.FlowActionCallRegistrationsFromDescriptors(
			descriptors,
			automation.DefaultAppFlowActionCallHandlerImplementations()...,
		)
		if err != nil {
			return nil, fmt.Errorf("resolve installed flow action-call registrations for metadata program check: %w", err)
		}
		registrations = append(registrations, installedRegistrations...)
	}
	descriptors, err := automation.EffectiveFlowActionCallRegistrationDescriptors(registrations...)
	if err != nil {
		return nil, fmt.Errorf("resolve effective flow action-call descriptors for metadata program check: %w", err)
	}
	for _, descriptor := range descriptors {
		addRegisteredActionCallType(out, descriptor.ActionType)
	}
	return out, nil
}

func addRegisteredActionCallType(out map[string]string, actionType string) {
	actionType = strings.TrimSpace(actionType)
	if actionType == "" {
		return
	}
	out[strings.ToLower(actionType)] = actionType
}

func normalizeMetadataProgramOwnerPacks(ownerPacks []string) []string {
	out := make([]string, 0, len(ownerPacks))
	seen := map[string]struct{}{}
	for _, ownerPack := range ownerPacks {
		ownerPack = strings.TrimSpace(ownerPack)
		if ownerPack == "" {
			continue
		}
		key := strings.ToLower(ownerPack)
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, ownerPack)
	}
	return out
}

func setupActionSchemaFieldsToContract(fields []standardaction.SchemaField) []sharedcontract.StandardActionSchemaField {
	out := make([]sharedcontract.StandardActionSchemaField, 0, len(fields))
	for _, field := range fields {
		out = append(out, sharedcontract.StandardActionSchemaField{
			Name:                      field.Name,
			Type:                      setupNormalizeMetadataProgramDataType(field.Type),
			Required:                  field.Required,
			ReferenceTo:               field.ReferenceTo,
			Label:                     field.Label,
			UISection:                 field.UISection,
			UIMode:                    field.UIMode,
			TargetObject:              field.TargetObject,
			TargetFieldAPIName:        field.TargetFieldAPIName,
			RequiredPicklistTag:       field.RequiredPicklistTag,
			MappingSourceObject:       field.MappingSourceObject,
			MappingActionType:         field.MappingActionType,
			IsCollection:              field.IsCollection,
			PrefillSourceFieldAPIName: field.PrefillSourceFieldAPIName,
			TargetRequired:            field.TargetRequired,
			TargetDefaulted:           field.TargetDefaulted,
			Defaulted:                 field.Defaulted,
			HasDefaultValue:           field.HasDefaultValue,
			DefaultValue:              field.DefaultValue,
		})
	}
	return out
}

func setupNormalizeFlowMetadataForProgramCheck(metadata models.FlowMetadata) models.FlowMetadata {
	if len(metadata.Variables) > 0 {
		metadata.Variables = append([]models.FlowVariable(nil), metadata.Variables...)
		for i := range metadata.Variables {
			metadata.Variables[i].DataType = setupNormalizeMetadataProgramDataType(metadata.Variables[i].DataType)
		}
	}
	if len(metadata.Formulas) > 0 {
		metadata.Formulas = append([]models.FlowFormula(nil), metadata.Formulas...)
		for i := range metadata.Formulas {
			metadata.Formulas[i].DataType = setupNormalizeMetadataProgramDataType(metadata.Formulas[i].DataType)
		}
	}
	return metadata
}

func setupNormalizeMetadataProgramDataType(dataType string) string {
	normalized, ok := setupNormalizeMetadataProgramDataTypeInner(strings.TrimSpace(dataType))
	if !ok {
		return dataType
	}
	return normalized
}

func setupNormalizeMetadataProgramDataTypeInner(dataType string) (string, bool) {
	if strings.EqualFold(dataType, "SObject") {
		return "Object", true
	}
	if strings.HasSuffix(dataType, "[]") {
		inner := strings.TrimSpace(strings.TrimSuffix(dataType, "[]"))
		if normalized, ok := setupNormalizeMetadataProgramDataTypeInner(inner); ok {
			return normalized + "[]", true
		}
		return "", false
	}
	for _, prefix := range []string{"array", "list", "collection"} {
		if normalized, ok := setupNormalizeBracketedMetadataProgramDataType(dataType, prefix); ok {
			return normalized, true
		}
	}
	return "", false
}

func setupNormalizeBracketedMetadataProgramDataType(dataType string, prefix string) (string, bool) {
	open := strings.Index(dataType, "<")
	if open <= 0 || !strings.HasSuffix(dataType, ">") {
		return "", false
	}
	if !strings.EqualFold(strings.TrimSpace(dataType[:open]), prefix) {
		return "", false
	}
	inner := strings.TrimSpace(dataType[open+1 : len(dataType)-1])
	normalized, ok := setupNormalizeMetadataProgramDataTypeInner(inner)
	if !ok {
		return "", false
	}
	return strings.TrimSpace(dataType[:open]) + "<" + normalized + ">", true
}

type metadataProgramDiagnosticsError struct {
	diagnostics programchecker.Diagnostics
}

func (e metadataProgramDiagnosticsError) Error() string {
	if len(e.diagnostics) == 0 {
		return "metadata program check failed"
	}
	lines := make([]string, 0, min(len(e.diagnostics), 5))
	for i, diag := range e.diagnostics {
		if i >= 5 {
			break
		}
		parts := []string{}
		if diag.OwnerPack != "" {
			parts = append(parts, diag.OwnerPack)
		}
		if diag.ComponentPath != "" {
			parts = append(parts, diag.ComponentPath)
		}
		if diag.JSONPointer != "" {
			parts = append(parts, diag.JSONPointer)
		}
		if diag.Code != "" {
			parts = append(parts, diag.Code)
		}
		prefix := strings.Join(parts, " ")
		if prefix == "" {
			prefix = "metadata_program"
		}
		message := strings.TrimSpace(diag.Message)
		if message == "" {
			message = diag.Reference
		}
		lines = append(lines, fmt.Sprintf("%s: %s", prefix, message))
	}
	if len(e.diagnostics) > len(lines) {
		lines = append(lines, fmt.Sprintf("%d more metadata program error(s)", len(e.diagnostics)-len(lines)))
	}
	return "metadata program check failed:\n  - " + strings.Join(lines, "\n  - ")
}
