package packs

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

	"github.com/nexuscrm/kernel/pkg/formula"
	"github.com/nexuscrm/shared/pkg/models"
)

type InstalledPackResolverInput struct {
	Name               string
	Category           Category
	AppliedPayloadHash string
	// ManifestFormatVersion is the installed-row contract version. Installed
	// source resolution uses it to scope transitional compat for legacy
	// active-scalar formula payloads to rows that were actually written under
	// the v1 contract.
	ManifestFormatVersion string
	SourceKind            string
	SourceURI             string
	SourceRevision        string
}

type InstalledPackResolverOptions struct {
	AllowBundledFallback bool
	// PackSourceOptions carries operation-scoped exact source authority. A nil
	// value preserves the default fail-closed behavior for release_repo.
	PackSourceOptions                             *PackSourceOptions
	RuntimeFormulaFunctionBundlesForValidation    []formula.FunctionBundle
	HasRuntimeFormulaFunctionBundlesForValidation bool
	ObjectSchemaResolver                          func(string) *models.ObjectMetadata
	ComponentKindRegistrationSet                  *ComponentKindRegistrationSet
}

// ResolveInstalledSourceBackedPack resolves an installed pack from persisted
// source identity instead of the bundled pack registry.
func ResolveInstalledSourceBackedPack(name string, category Category, sourceKind string, sourceURI string, opts ...SourceBackedPackOption) (*SourceBackedPack, error) {
	return resolveInstalledSourceBackedPackWithStoredIdentity(name, category, sourceKind, sourceURI, "", "", opts...)
}

// ResolveInstalledSourceBackedPackForInstalledRow resolves a source-backed pack
// using the installed row's persisted manifest-format version as the
// compatibility boundary. Use this only when resolving an already-installed
// row; fresh source authoring must not call this helper.
func ResolveInstalledSourceBackedPackForInstalledRow(name string, category Category, manifestFormatVersion string, sourceKind string, sourceURI string, opts ...SourceBackedPackOption) (*SourceBackedPack, error) {
	return resolveInstalledSourceBackedPackWithStoredIdentity(name, category, sourceKind, sourceURI, manifestFormatVersion, "", opts...)
}

func ResolveInstalledSourceBackedPackForInstalledRowWithFormulaBundles(name string, category Category, manifestFormatVersion string, sourceKind string, sourceURI string, formulaFunctionBundlesForValidation []formula.FunctionBundle, hasFormulaFunctionBundlesForValidation bool, opts ...SourceBackedPackOption) (*SourceBackedPack, error) {
	return resolveInstalledSourceBackedPackWithStoredIdentityAndFormulaBundles(
		name,
		category,
		sourceKind,
		sourceURI,
		manifestFormatVersion,
		"",
		formulaFunctionBundlesForValidation,
		hasFormulaFunctionBundlesForValidation,
		opts...,
	)
}

func ResolveInstalledSourceBackedPackForInstalledRowAtSourceRevisionWithFormulaBundles(name string, category Category, manifestFormatVersion string, sourceKind string, sourceURI string, sourceRevision string, formulaFunctionBundlesForValidation []formula.FunctionBundle, hasFormulaFunctionBundlesForValidation bool, opts ...SourceBackedPackOption) (*SourceBackedPack, error) {
	return ResolveInstalledSourceBackedPackForInstalledRowAtSourceRevisionWithPackSourceOptionsAndFormulaBundles(
		name,
		category,
		manifestFormatVersion,
		sourceKind,
		sourceURI,
		sourceRevision,
		DefaultPackSourceOptions(),
		formulaFunctionBundlesForValidation,
		hasFormulaFunctionBundlesForValidation,
		opts...,
	)
}

func ResolveInstalledSourceBackedPackForInstalledRowAtSourceRevisionWithPackSourceOptions(name string, category Category, manifestFormatVersion string, sourceKind string, sourceURI string, sourceRevision string, packSourceOptions PackSourceOptions, opts ...SourceBackedPackOption) (*SourceBackedPack, error) {
	return ResolveInstalledSourceBackedPackForInstalledRowAtSourceRevisionWithPackSourceOptionsAndFormulaBundles(
		name,
		category,
		manifestFormatVersion,
		sourceKind,
		sourceURI,
		sourceRevision,
		packSourceOptions,
		nil,
		false,
		opts...,
	)
}

func ResolveInstalledSourceBackedPackForInstalledRowAtSourceRevisionWithPackSourceOptionsAndComponentKindRegistrationSet(name string, category Category, manifestFormatVersion string, sourceKind string, sourceURI string, sourceRevision string, packSourceOptions PackSourceOptions, registrations ComponentKindRegistrationSet, opts ...SourceBackedPackOption) (*SourceBackedPack, error) {
	return resolveInstalledSourceBackedPackWithStoredIdentityAndOptionsAndFormulaBundlesAndRegistrationSet(
		name, category, sourceKind, sourceURI, manifestFormatVersion, sourceRevision,
		packSourceOptions, nil, false, &registrations, opts...,
	)
}

func ResolveInstalledSourceBackedPackForInstalledRowAtSourceRevisionWithPackSourceOptionsAndFormulaBundles(name string, category Category, manifestFormatVersion string, sourceKind string, sourceURI string, sourceRevision string, packSourceOptions PackSourceOptions, formulaFunctionBundlesForValidation []formula.FunctionBundle, hasFormulaFunctionBundlesForValidation bool, opts ...SourceBackedPackOption) (*SourceBackedPack, error) {
	return resolveInstalledSourceBackedPackWithStoredIdentityAndOptionsAndFormulaBundles(
		name,
		category,
		sourceKind,
		sourceURI,
		manifestFormatVersion,
		sourceRevision,
		packSourceOptions,
		formulaFunctionBundlesForValidation,
		hasFormulaFunctionBundlesForValidation,
		opts...,
	)
}

func ResolveInstalledSourceBackedPackForInstalledRowAtSourceRevisionWithPackSourceOptionsAndFormulaBundlesAndComponentKindRegistrationSet(name string, category Category, manifestFormatVersion string, sourceKind string, sourceURI string, sourceRevision string, packSourceOptions PackSourceOptions, formulaFunctionBundlesForValidation []formula.FunctionBundle, hasFormulaFunctionBundlesForValidation bool, registrations ComponentKindRegistrationSet, opts ...SourceBackedPackOption) (*SourceBackedPack, error) {
	return resolveInstalledSourceBackedPackWithStoredIdentityAndOptionsAndFormulaBundlesAndRegistrationSet(
		name, category, sourceKind, sourceURI, manifestFormatVersion, sourceRevision,
		packSourceOptions, formulaFunctionBundlesForValidation, hasFormulaFunctionBundlesForValidation, &registrations, opts...,
	)
}

func resolveInstalledSourceBackedPackWithStoredManifestVersion(name string, category Category, sourceKind string, sourceURI string, storedManifestFormatVersion string, opts ...SourceBackedPackOption) (*SourceBackedPack, error) {
	return resolveInstalledSourceBackedPackWithStoredIdentity(name, category, sourceKind, sourceURI, storedManifestFormatVersion, "", opts...)
}

func resolveActiveInstalledSourceBackedPackWithStoredManifestVersion(ctx context.Context, db *sql.DB, name string, category Category, sourceKind string, sourceURI string, storedManifestFormatVersion string, opts ...SourceBackedPackOption) (*SourceBackedPack, error) {
	return resolveActiveInstalledSourceBackedPackWithStoredManifestVersionAndComponentKindRegistrationSet(
		ctx, db, name, category, sourceKind, sourceURI, storedManifestFormatVersion, nil, opts...,
	)
}

func resolveActiveInstalledSourceBackedPackWithStoredManifestVersionAndComponentKindRegistrationSet(ctx context.Context, db *sql.DB, name string, category Category, sourceKind string, sourceURI string, storedManifestFormatVersion string, registrations *ComponentKindRegistrationSet, opts ...SourceBackedPackOption) (*SourceBackedPack, error) {
	packSourceOptions, ok := OperationPackSourceOptions(ctx)
	if !ok {
		packSourceOptions = DefaultPackSourceOptions()
	}
	packSourceOptions.ObjectSchemaResolver = sourceBackedVerifySchemaFallback(ctx, VerifyDeps{DB: db})
	return resolveInstalledSourceBackedPackWithStoredIdentityAndOptionsAndFormulaBundlesAndRegistrationSet(
		name, category, sourceKind, sourceURI, storedManifestFormatVersion, "",
		packSourceOptions, nil, false, registrations, opts...,
	)
}

func ResolveInstalledSourceBackedPackForInstalledRowAtSourceRevision(name string, category Category, manifestFormatVersion string, sourceKind string, sourceURI string, sourceRevision string, opts ...SourceBackedPackOption) (*SourceBackedPack, error) {
	return ResolveInstalledSourceBackedPackForInstalledRowAtSourceRevisionWithPackSourceOptions(name, category, manifestFormatVersion, sourceKind, sourceURI, sourceRevision, DefaultPackSourceOptions(), opts...)
}

// resolveInstalledSourceBackedPackWithStoredIdentity is for installed-row
// resolution only. Fresh source authoring must not use it. The stored manifest
// version is the compat boundary: only rows that were persisted under the v1
// contract may keep reading the pre-957 active-scalar formula shape during
// installed-pack resolution. Revision-addressable source kinds, such as
// official_repo, use stored source revisions to reconstruct the historical pack
// surface. local_path rows read the current SourceURI; their stored revision is
// install provenance from the enclosing checkout, not a stable source address.
func resolveInstalledSourceBackedPackWithStoredIdentity(name string, category Category, sourceKind string, sourceURI string, storedManifestFormatVersion string, storedSourceRevision string, opts ...SourceBackedPackOption) (*SourceBackedPack, error) {
	return resolveInstalledSourceBackedPackWithStoredIdentityAndFormulaBundles(name, category, sourceKind, sourceURI, storedManifestFormatVersion, storedSourceRevision, nil, false, opts...)
}

func resolveInstalledSourceBackedPackWithStoredIdentityAndFormulaBundles(name string, category Category, sourceKind string, sourceURI string, storedManifestFormatVersion string, storedSourceRevision string, formulaFunctionBundlesForValidation []formula.FunctionBundle, hasFormulaFunctionBundlesForValidation bool, opts ...SourceBackedPackOption) (*SourceBackedPack, error) {
	return resolveInstalledSourceBackedPackWithStoredIdentityAndOptionsAndFormulaBundles(name, category, sourceKind, sourceURI, storedManifestFormatVersion, storedSourceRevision, DefaultPackSourceOptions(), formulaFunctionBundlesForValidation, hasFormulaFunctionBundlesForValidation, opts...)
}

func resolveInstalledSourceBackedPackWithStoredIdentityAndOptions(name string, category Category, sourceKind string, sourceURI string, storedManifestFormatVersion string, storedSourceRevision string, packSourceOptions PackSourceOptions, opts ...SourceBackedPackOption) (*SourceBackedPack, error) {
	return resolveInstalledSourceBackedPackWithStoredIdentityAndOptionsAndFormulaBundles(name, category, sourceKind, sourceURI, storedManifestFormatVersion, storedSourceRevision, packSourceOptions, nil, false, opts...)
}

func resolveInstalledSourceBackedPackWithStoredIdentityAndOptionsAndFormulaBundles(name string, category Category, sourceKind string, sourceURI string, storedManifestFormatVersion string, storedSourceRevision string, packSourceOptions PackSourceOptions, formulaFunctionBundlesForValidation []formula.FunctionBundle, hasFormulaFunctionBundlesForValidation bool, opts ...SourceBackedPackOption) (*SourceBackedPack, error) {
	return resolveInstalledSourceBackedPackWithStoredIdentityAndOptionsAndFormulaBundlesAndRegistrationSet(
		name, category, sourceKind, sourceURI, storedManifestFormatVersion, storedSourceRevision,
		packSourceOptions, formulaFunctionBundlesForValidation, hasFormulaFunctionBundlesForValidation, nil, opts...,
	)
}

func resolveInstalledSourceBackedPackWithStoredIdentityAndOptionsAndFormulaBundlesAndRegistrationSet(name string, category Category, sourceKind string, sourceURI string, storedManifestFormatVersion string, storedSourceRevision string, packSourceOptions PackSourceOptions, formulaFunctionBundlesForValidation []formula.FunctionBundle, hasFormulaFunctionBundlesForValidation bool, registrations *ComponentKindRegistrationSet, opts ...SourceBackedPackOption) (*SourceBackedPack, error) {
	name = strings.TrimSpace(name)
	sourceKind = strings.TrimSpace(sourceKind)
	sourceURI = strings.TrimSpace(sourceURI)
	storedManifestFormatVersion = strings.TrimSpace(storedManifestFormatVersion)
	storedSourceRevision = strings.TrimSpace(storedSourceRevision)
	if sourceKind == "" {
		return nil, fmt.Errorf("missing SourceKind")
	}
	if sourceURI == "" {
		return nil, fmt.Errorf("missing SourceURI")
	}
	allowStoredManifestV1Compat := storedManifestFormatVersion == SupportedManifestFormatVersion
	validationOpts := packSourceOptions
	if registrations != nil {
		validationOpts = registrations.withManagedValidationInputs(packSourceOptions)
	}
	source, diagnostics := resolveStoredPackSourceWithOptions(sourceKind, sourceURI, storedSourceRevision, validationOpts)
	diagnostics = append(diagnostics, validateStoredSourceHostRequirements(source, validationOpts)...)
	reportOpts := append([]SourceBackedPackOption(nil), opts...)
	if hasFormulaFunctionBundlesForValidation {
		reportOpts = append(reportOpts, WithRuntimeFormulaFunctionBundlesForValidation(formulaFunctionBundlesForValidation))
	}
	// Installed rows written before folder_type.createPermission existed still
	// need to resolve so readiness can fail with its payload guard instead of
	// breaking source reconstruction. Runtime metadata has no broad compat:
	// missing values must fail before a pack can be verified or reapplied.
	reportOpts = append(reportOpts, withLegacyFolderTypeMissingCreatePermissionCompat())
	// The report build internally constructs the pack from reportOpts (see
	// pack_source.go NewSourceBackedPack), so the OAuth-scope installed-row compat
	// must be present here too — not only in packOpts below. Without it, an
	// old-shape oauth_custom_scope payload (route_patterns without route_family_prefix)
	// fails closed during report construction and the resolver returns before the
	// packOpts compat is ever applied.
	reportOpts = append(reportOpts, withLegacyOAuthScopeMissingRouteFamilyPrefixCompat())
	report := buildInstallablePackSourceReportFromResolvedSourceWithFormulaFunctionBundlesAndRegistrationSet(
		source,
		diagnostics,
		packSourceOptions,
		allowStoredManifestV1Compat,
		true,
		formulaFunctionBundlesForValidation,
		hasFormulaFunctionBundlesForValidation,
		registrations,
		reportOpts...,
	)
	if report.HasErrors() {
		return nil, fmt.Errorf("%s", report.Error())
	}
	if report.Source == nil || report.Source.Manifest == nil {
		return nil, fmt.Errorf("source did not produce a manifest")
	}
	if got := strings.TrimSpace(report.Source.Manifest.Name); got != name {
		return nil, fmt.Errorf("source/name drift: manifest name %q, installed row %q", got, name)
	}
	packOpts := append([]SourceBackedPackOption(nil), opts...)
	if registrations != nil {
		packOpts = append(packOpts, withComponentKindRegistrationSet(*registrations, nil))
	}
	if hasFormulaFunctionBundlesForValidation {
		packOpts = append(packOpts, WithRuntimeFormulaFunctionBundlesForValidation(formulaFunctionBundlesForValidation))
	}
	if len(packSourceOptions.FlowActionCallRegistrations) > 0 {
		packOpts = append(packOpts, withFlowActionCallRegistrations(packSourceOptions.FlowActionCallRegistrations))
	}
	if len(packSourceOptions.FlowActionCallHandlerImplementations) > 0 {
		packOpts = append(packOpts, withFlowActionCallHandlerImplementations(packSourceOptions.FlowActionCallHandlerImplementations))
	}
	formulaCompat := installedPackFormulaCompatForSource(report.Source, allowStoredManifestV1Compat)
	if formulaCompat.allowLegacyAggregateFormulaAuthoring {
		packOpts = append(packOpts, withLegacyAggregateFormulaAuthoringCompat())
	}
	packOpts = append(packOpts, withLegacyFolderTypeMissingCreatePermissionCompat())
	packOpts = append(packOpts, withLegacyOAuthScopeMissingRouteFamilyPrefixCompat())
	packOpts = append(packOpts, withConnectorPushValidationSchemaResolver(connectorPushValidationSchemaResolverForSource(report.Source, validationOpts, true)))
	pack, err := NewSourceBackedPack(report.Source, packOpts...)
	if err != nil {
		return nil, fmt.Errorf("construct source-backed pack: %w", err)
	}
	if pack == nil {
		return nil, fmt.Errorf("source did not produce an installable pack")
	}
	if category != "" && pack.Category() != category {
		return nil, fmt.Errorf("category drift: installed=%q source=%q", category, pack.Category())
	}
	return pack, nil
}

func validateStoredSourceHostRequirements(source *ResolvedPackSource, opts PackSourceOptions) []PackDiagnostic {
	if source == nil || source.Manifest == nil {
		return nil
	}
	var diagnostics []PackDiagnostic
	diagnostics = append(diagnostics, validateHostRequirements(source.Manifest.HostRequirements, opts.HostPrimitiveRegistry)...)
	diagnostics = append(diagnostics, validateReferencedHostRequirements(source, source.Manifest.HostRequirements)...)
	return diagnostics
}

// ResolveInstalledPack resolves the authoritative pack implementation for one
// installed-pack row. App packs must carry source identity; platform binaries
// do not synthesize CRM/application pack payloads.
func ResolveInstalledPack(input InstalledPackResolverInput, opts InstalledPackResolverOptions) (Pack, error) {
	name := strings.TrimSpace(input.Name)
	category := input.Category
	sourceKind := strings.TrimSpace(input.SourceKind)
	sourceURI := strings.TrimSpace(input.SourceURI)

	if sourceKind != "" || sourceURI != "" {
		packSourceOptions := DefaultPackSourceOptions()
		if opts.PackSourceOptions != nil {
			packSourceOptions = cloneOperationPackSourceOptions(*opts.PackSourceOptions)
		}
		packSourceOptions.ObjectSchemaResolver = opts.ObjectSchemaResolver
		pack, err := resolveInstalledSourceBackedPackWithStoredIdentityAndOptionsAndFormulaBundlesAndRegistrationSet(
			name,
			category,
			sourceKind,
			sourceURI,
			input.ManifestFormatVersion,
			input.SourceRevision,
			packSourceOptions,
			opts.RuntimeFormulaFunctionBundlesForValidation,
			opts.HasRuntimeFormulaFunctionBundlesForValidation,
			opts.ComponentKindRegistrationSet,
		)
		if err != nil {
			return nil, fmt.Errorf("resolve installed source-backed pack %s: %w", name, err)
		}
		if strings.TrimSpace(input.AppliedPayloadHash) != "" {
			if _, err := ValidateStoredPackPayloadHash(pack, input.AppliedPayloadHash); err != nil {
				return nil, fmt.Errorf("installed source-backed pack %s payload hash: %w", name, err)
			}
		}
		return pack, nil
	}

	return nil, fmt.Errorf("installed pack %s has no source identity", name)
}
