package packs

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/nexuscrm/automation"
	"github.com/nexuscrm/files"
	kernelconnector "github.com/nexuscrm/kernel/pkg/connector"
	"github.com/nexuscrm/kernel/pkg/formula"
	"github.com/nexuscrm/metadata"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/nexuscrm/shared/pkg/searchmetadata"
	connector "github.com/pingcap-inc/nexuscrm-contract/pkg/connector"
	"github.com/pingcap-inc/nexuscrm-controlplane/internal/offlinecandidateauthority"
)

// SourceBackedPack is a transient operation-local adapter from a resolved pack
// manifest to the existing Pack/component lifecycle. It must never be inserted
// into the binary KnownPackByName registry.
type SourceBackedPack struct {
	source                                *ResolvedPackSource
	manifest                              *PackManifest
	constructionComplete                  bool
	components                            []PackComponent
	fieldCompat                           packFieldAuthoringCompat
	connectorCalloutProfileCompat         connectorCalloutProfileAuthoringCompat
	folderTypeCompat                      folderTypeAuthoringCompat
	oauthCustomScopeCompat                oauthCustomScopeAuthoringCompat
	connectorPushValidationSchema         func(string) *models.ObjectMetadata
	stoppedSourceSetConstructionApplyOnly bool
	// sweepComponents holds pack-level synthetic install steps that must run
	// regardless of which kinds the current manifest declares. They are kept
	// off `components` so they don't pollute Components(), DeclaredComponents()
	// (the L0 ownership oracle), or payload-hash inputs - but they are folded
	// into InstallPlan() and Verify() so apply + verify still see them.
	//
	// Current use: the record_type_picklist_value pack-level sweeper for
	// Codex's second-round HIGH on PR #919. Without it, a pack version that
	// drops every record_type_picklist_value component would leave stale
	// `_System_RecordTypePicklistValue` rows live because the existing
	// per-component batched apply only fires when at least one such component
	// is in the manifest.
	sweepComponents                              []PackComponent
	runtimeObjectPayloads                        [][]byte
	ownedObjectAPINames                          []string
	validationRuleKeys                           []ValidationRuleBusinessKey
	relatedListRules                             []metadata.RelatedListRule
	searchScopeOrder                             []metadata.SearchScopeOrderItem
	searchAllDefaultFields                       []searchmetadata.SearchAllDefaultFieldsDeclaration
	formulaFunctionBundles                       []formula.FunctionBundle
	formulaFunctionBundlesForValidation          []formula.FunctionBundle
	hasFormulaFunctionBundlesForValidation       bool
	hrefRewriteFields                            []files.HrefRewriteField
	runtimeStartupShallowRows                    []runtimeStartupShallowDeclaredRow
	runtimeStartupShallowInactiveValidationRules map[string]bool
	standardFlowPayloads                         map[string][]byte
	allowOwnerlessObjectAPI                      map[string]bool
	connectorCapabilityDeclarations              []ConnectorCapabilityComponentPayload
	connectedAppDeclarations                     []sourceBackedConnectedAppDeclarationRow
	connectedAppOwnershipKeys                    []ConnectedAppOwnershipKey
	connectorInboundHandlerConfigDeclarations    []ConnectorInboundHandlerConfigComponentPayload
	connectorInboundHandlerConfigBatch           *connectorInboundHandlerConfigBatch
	connectorInboundRouteBatch                   *connectorInboundRouteBatch
	connectorFieldMappingDeclarations            []connector.ConnectorFieldMappingInput
	connectorPushContractDeclarations            []connector.ConnectorPushDurableMetadataContract
	connectorPushHistoricalDecls                 []sourceBackedConnectorPushHistoricalReconciliationDeclaration
	connectorPushHistoricalBindings              []kernelconnector.ConnectorPushHistoricalReconciliationBinding
	flowActionCallRegistrations                  []automation.FlowActionCallRegistration
	flowActionCallHandlerImplementations         []automation.FlowActionCallHandlerImplementation
	componentRegistrationSet                     ComponentKindRegistrationSet
	componentRegistrationSetConfigured           bool
	componentRegistrationSetErr                  error
	// externalBuildRoutes optionally diverts selected component kinds to an
	// external (contract-only) build handler instead of the internal
	// method-value dispatch. Nil/empty by default (production path unchanged):
	// the engine builds every kind internally. Set in tests / future wiring to
	// route specific kinds through the cp.ComponentBuildHandler seam.
	externalBuildRoutes map[ComponentKind]externalBuildRoute
}

// ConnectorCapabilityDeclarations returns declared connector_capability payloads (empty for non-ConnectorPacks).
func (p *SourceBackedPack) ConnectorCapabilityDeclarations() []ConnectorCapabilityComponentPayload {
	return append([]ConnectorCapabilityComponentPayload(nil), p.connectorCapabilityDeclarations...)
}

// ConnectorInboundHandlerConfigDeclarations returns declared
// connector_inbound_handler_config payloads (empty for non-ConnectorPacks).
func (p *SourceBackedPack) ConnectorInboundHandlerConfigDeclarations() []ConnectorInboundHandlerConfigComponentPayload {
	return cloneConnectorInboundHandlerConfigDeclarations(p.connectorInboundHandlerConfigDeclarations)
}

func (p *SourceBackedPack) ConnectorPushContractDeclarations() []connector.ConnectorPushDurableMetadataContract {
	return cloneConnectorPushContractDeclarations(p.connectorPushContractDeclarations)
}

// ConnectorPushHistoricalReconciliationBindings returns immutable bindings
// assembled from this exact source-backed pack revision.
func (p *SourceBackedPack) ConnectorPushHistoricalReconciliationBindings() []kernelconnector.ConnectorPushHistoricalReconciliationBinding {
	return cloneConnectorPushHistoricalReconciliationBindings(p.connectorPushHistoricalBindings)
}

// ConnectorCalloutProfilePayloads returns the manifest-authored connector
// callout profile payloads for this exact source-backed pack revision.
func (p *SourceBackedPack) ConnectorCalloutProfilePayloads() ([][]byte, error) {
	out := make([][]byte, 0)
	for _, component := range p.components {
		if component.Kind() != ComponentKindConnectorCalloutProfile {
			continue
		}
		payload, err := component.ExpectedPayload()
		if err != nil {
			return nil, err
		}
		out = append(out, payload)
	}
	return out, nil
}

type ValidationRuleBusinessKey struct {
	ObjectAPIName string
	Name          string
}

type ConnectedAppOwnershipKey struct {
	ID                      string
	Name                    string
	ConsumerKey             string
	CallbackURL             string
	RedirectURIs            []string
	Scopes                  []string
	IsActive                bool
	ClientType              string
	RequirePKCE             bool
	TokenEndpointAuthMethod string
	AllowedGrantTypes       []string
	ConsentPolicy           string
	ConsentVersion          int
	IssueRefreshTokens      bool
	PermittedUsers          string
	ConsumerSecretPresent   bool
}

type objectSpecVerifyError struct {
	errs []error
}

func (e *objectSpecVerifyError) Error() string {
	if e == nil || len(e.errs) == 0 {
		return "object spec verify failed"
	}
	lines := make([]string, 0, len(e.errs))
	for _, err := range e.errs {
		if err != nil {
			lines = append(lines, err.Error())
		}
	}
	if len(lines) == 0 {
		return "object spec verify failed"
	}
	return fmt.Sprintf("%s", strings.Join(lines, "\n  - "))
}

func (e *objectSpecVerifyError) Unwrap() []error {
	if e == nil || len(e.errs) == 0 {
		return nil
	}
	out := make([]error, 0, len(e.errs))
	for _, err := range e.errs {
		if err != nil {
			out = append(out, err)
		}
	}
	return out
}

// SourceBackedPackOption configures one pack while NewSourceBackedPack owns its
// construction. Applying a package-provided option after construction panics
// instead of creating state that disagrees with the built components.
type SourceBackedPackOption func(*SourceBackedPack)

// WithAllowedOwnerlessObjectAPIs allows apply to reconcile ownerless partial
// object/table/field surfaces that preflight has already classified as
// same-pack retry-compatible or unchanged active same-pack state.
func WithAllowedOwnerlessObjectAPIs(objectAPINames []string) SourceBackedPackOption {
	return func(pack *SourceBackedPack) {
		pack.requireSourceBackedPackConstruction()
		if pack.allowOwnerlessObjectAPI == nil {
			pack.allowOwnerlessObjectAPI = map[string]bool{}
		}
		for _, name := range objectAPINames {
			name = strings.TrimSpace(name)
			if name != "" {
				pack.allowOwnerlessObjectAPI[strings.ToLower(name)] = true
			}
		}
	}
}

// RebuildSourceBackedPackWithAllowedOwnerlessObjectAPIs rebuilds one already
// constructed source pack from its constructor-owned source snapshot. It is
// used only after the caller has admitted exact same-pack ownerless state; no
// filesystem or repository source is resolved again.
func RebuildSourceBackedPackWithAllowedOwnerlessObjectAPIs(source *SourceBackedPack, objectAPINames []string) (*SourceBackedPack, error) {
	if source == nil || !source.constructionComplete || source.source == nil {
		return nil, fmt.Errorf("source-backed pack is incomplete")
	}
	opts := []SourceBackedPackOption{
		WithAllowedOwnerlessObjectAPIs(objectAPINames),
		WithComponentKindRegistrationsFromSourcePack(source),
		withFlowActionCallRegistrations(source.flowActionCallRegistrations),
		withFlowActionCallHandlerImplementations(source.flowActionCallHandlerImplementations),
		withConnectorPushValidationSchemaResolver(source.connectorPushValidationSchema),
	}
	if source.hasFormulaFunctionBundlesForValidation {
		opts = append(opts, WithRuntimeFormulaFunctionBundlesForValidation(source.formulaFunctionBundlesForValidation))
	}
	if source.fieldCompat.allowLegacyAggregateFormulaAuthoring {
		opts = append(opts, withLegacyAggregateFormulaAuthoringCompat())
	}
	if source.connectorCalloutProfileCompat.allowLegacyMissingBatchIDResponseField {
		opts = append(opts, withLegacyConnectorCalloutBatchIDResponseFieldCompat())
	}
	if source.folderTypeCompat.allowLegacyMissingCreatePermission {
		opts = append(opts, withLegacyFolderTypeMissingCreatePermissionCompat())
	}
	if source.oauthCustomScopeCompat.allowLegacyMissingRouteFamilyPrefix {
		opts = append(opts, withLegacyOAuthScopeMissingRouteFamilyPrefixCompat())
	}
	return NewSourceBackedPack(source.source, opts...)
}

func WithRuntimeFormulaFunctionBundlesForValidation(bundles []formula.FunctionBundle) SourceBackedPackOption {
	return func(pack *SourceBackedPack) {
		pack.requireSourceBackedPackConstruction()
		pack.formulaFunctionBundlesForValidation = formula.CloneFunctionBundles(bundles)
		pack.hasFormulaFunctionBundlesForValidation = true
	}
}

func withFlowActionCallRegistrations(registrations []automation.FlowActionCallRegistration) SourceBackedPackOption {
	return func(pack *SourceBackedPack) {
		pack.requireSourceBackedPackConstruction()
		pack.flowActionCallRegistrations = append([]automation.FlowActionCallRegistration(nil), registrations...)
	}
}

func withFlowActionCallHandlerImplementations(implementations []automation.FlowActionCallHandlerImplementation) SourceBackedPackOption {
	return func(pack *SourceBackedPack) {
		pack.requireSourceBackedPackConstruction()
		pack.flowActionCallHandlerImplementations = append([]automation.FlowActionCallHandlerImplementation(nil), implementations...)
	}
}

// WithFlowActionCallRuntimeFromPackSourceOptions carries the runtime actionCall
// registry context used during source validation into explicit SourceBackedPack
// rebuilds. Callers that rebuild a pack after validation must preserve this
// context so source-backed handler declarations are still checked against the
// active distribution runtime.
func WithFlowActionCallRuntimeFromPackSourceOptions(opts PackSourceOptions) SourceBackedPackOption {
	return func(pack *SourceBackedPack) {
		pack.requireSourceBackedPackConstruction()
		pack.flowActionCallRegistrations = append([]automation.FlowActionCallRegistration(nil), opts.FlowActionCallRegistrations...)
		pack.flowActionCallHandlerImplementations = append([]automation.FlowActionCallHandlerImplementation(nil), opts.FlowActionCallHandlerImplementations...)
	}
}

func withLegacyAggregateFormulaAuthoringCompat() SourceBackedPackOption {
	return func(pack *SourceBackedPack) {
		pack.requireSourceBackedPackConstruction()
		pack.fieldCompat.allowLegacyAggregateFormulaAuthoring = true
	}
}

type connectorCalloutProfileAuthoringCompat struct {
	allowLegacyMissingBatchIDResponseField bool
}

func withLegacyConnectorCalloutBatchIDResponseFieldCompat() SourceBackedPackOption {
	return func(pack *SourceBackedPack) {
		pack.requireSourceBackedPackConstruction()
		pack.connectorCalloutProfileCompat.allowLegacyMissingBatchIDResponseField = true
	}
}

type folderTypeAuthoringCompat struct {
	allowLegacyMissingCreatePermission bool
}

func withLegacyFolderTypeMissingCreatePermissionCompat() SourceBackedPackOption {
	return func(pack *SourceBackedPack) {
		pack.requireSourceBackedPackConstruction()
		pack.folderTypeCompat.allowLegacyMissingCreatePermission = true
	}
}

type oauthCustomScopeAuthoringCompat struct {
	allowLegacyMissingRouteFamilyPrefix bool
}

func withLegacyOAuthScopeMissingRouteFamilyPrefixCompat() SourceBackedPackOption {
	return func(pack *SourceBackedPack) {
		pack.requireSourceBackedPackConstruction()
		pack.oauthCustomScopeCompat.allowLegacyMissingRouteFamilyPrefix = true
	}
}

func withConnectorPushValidationSchemaResolver(resolver func(string) *models.ObjectMetadata) SourceBackedPackOption {
	return func(pack *SourceBackedPack) {
		pack.requireSourceBackedPackConstruction()
		pack.connectorPushValidationSchema = resolver
	}
}

// WithStoppedSourceSetApplyOnly constructs the executable component plan for a
// stopped Runtime without adding source admission or post-write verification.
// Declared object surfaces are treated as in-place inputs to the real
// object-spec implementation, which collects deterministic compatibility
// conflicts before any ownership adoption and then fails the apply boundary.
// Other component Apply errors remain ordinary command failures.
func WithStoppedSourceSetApplyOnly(
	authority offlinecandidateauthority.StoppedSourceSetAuthority,
) SourceBackedPackOption {
	return func(pack *SourceBackedPack) {
		pack.requireSourceBackedPackConstruction()
		pack.stoppedSourceSetConstructionApplyOnly = authority.Valid()
		if pack.stoppedSourceSetConstructionApplyOnly && pack.allowOwnerlessObjectAPI == nil {
			pack.allowOwnerlessObjectAPI = map[string]bool{}
		}
	}
}

// WithConnectorPushValidationSchemaResolverForSource preserves the source and
// declared dependency lookup used by fresh installability validation when a
// caller needs to rebuild a requested source-backed pack with additional
// options. Installed-row recovery paths opt into live-schema fallback
// separately.
func WithConnectorPushValidationSchemaResolverForSource(source *ResolvedPackSource, opts PackSourceOptions) SourceBackedPackOption {
	return withConnectorPushValidationSchemaResolver(connectorPushValidationSchemaResolverForSource(source, opts, false))
}

func NewSourceBackedPack(source *ResolvedPackSource, opts ...SourceBackedPackOption) (*SourceBackedPack, error) {
	if source == nil || source.Manifest == nil {
		return nil, fmt.Errorf("source did not produce a manifest")
	}
	source = cloneResolvedPackSource(source)
	pack := &SourceBackedPack{
		source:   source,
		manifest: source.Manifest,
	}
	for _, opt := range opts {
		opt(pack)
	}
	if pack.componentRegistrationSetErr != nil {
		return nil, fmt.Errorf("component registration set: %w", pack.componentRegistrationSetErr)
	}
	if err := pack.installRegisteredBuildRoutes(); err != nil {
		return nil, fmt.Errorf("component registration routes: %w", err)
	}
	if err := pack.rejectUnroutedRegisteredKinds(); err != nil {
		return nil, err
	}
	if len(pack.flowActionCallHandlerImplementations) > 0 {
		registrations, err := flowActionCallRegistrationsFromSource(source, pack.flowActionCallHandlerImplementations)
		if err != nil {
			return nil, err
		}
		pack.flowActionCallRegistrations, err = mergeFlowActionCallRegistrationsByDescriptor(pack.flowActionCallRegistrations, registrations)
		if err != nil {
			return nil, err
		}
	}
	if err := pack.buildComponents(); err != nil {
		return nil, err
	}
	pack.stoppedSourceSetConstructionApplyOnly = false
	pack.constructionComplete = true
	return pack, nil
}

func (p *SourceBackedPack) rejectUnroutedRegisteredKinds() error {
	if !p.componentRegistrationSetConfigured {
		return nil
	}
	for _, component := range p.manifest.Components {
		if !p.componentRegistrationSet.reserves(component.Kind) {
			continue
		}
		route, routed := p.externalBuildRoutes[component.Kind]
		if !routed || route.handler == nil {
			return fmt.Errorf("component kind %q is externally registered but has no external build route", component.Kind)
		}
	}
	return nil
}

func (p *SourceBackedPack) Name() string {
	return strings.TrimSpace(p.manifest.Name)
}

func (p *SourceBackedPack) Category() Category {
	return p.manifest.Category
}

func (p *SourceBackedPack) Apply(ctx context.Context, deps ApplyDeps) error {
	plan, err := p.InstallPlan()
	if err != nil {
		return err
	}
	return plan.Apply(ctx, deps, InstallPlanHooks{})
}

func (p *SourceBackedPack) Verify(ctx context.Context, deps VerifyDeps) error {
	components, err := p.componentsWithLifecycleClosure()
	if err != nil {
		return err
	}
	return verifyPackComponents(ctx, deps, components)
}

func (p *SourceBackedPack) Components() []PackComponent {
	return append([]PackComponent(nil), p.components...)
}

// RepairObjectSpecSubstrate verifies the selected pack-owned object-spec
// components and replays substrate only for repairable installed-pack drift
// (for example missing pack-owned field rows or physical columns). It bypasses
// the install-time ownerless-surface preflight because ownership is already
// established on installed packs.
func (p *SourceBackedPack) RepairObjectSpecSubstrate(ctx context.Context, deps ApplyDeps, objectAPINames []string) error {
	if p == nil || len(objectAPINames) == 0 {
		return nil
	}
	objectSet := make(map[string]bool, len(objectAPINames))
	for _, objectAPIName := range objectAPINames {
		name := strings.ToLower(strings.TrimSpace(objectAPIName))
		if name != "" {
			objectSet[name] = true
		}
	}
	repairedObjectSet := map[string]bool{}
	for _, component := range p.components {
		objectComponent, ok := component.(*sourceBackedObjectSpecComponent)
		if !ok || !sourceBackedObjectSpecsTouchSet(objectComponent.specs, objectSet) {
			continue
		}
		if err := objectComponent.VerifyComponent(ctx, VerifyDeps{DB: deps.DB, DeepChecks: false}); err != nil {
			if !IsPackObjectInstalledPackRepairableDrift(err) {
				return fmt.Errorf("verify object_spec %s before substrate repair: %w", objectComponent.BusinessKey(), err)
			}
		} else {
			continue
		}
		if err := objectComponent.repairSubstrate(ctx, deps); err != nil {
			return err
		}
		for _, spec := range objectComponent.specs {
			name := strings.ToLower(strings.TrimSpace(spec.APIName))
			if name != "" && objectSet[name] {
				repairedObjectSet[name] = true
			}
		}
	}
	if err := p.replayListFieldsComponentsForObjects(ctx, deps, repairedObjectSet); err != nil {
		return err
	}
	return nil
}

func (p *SourceBackedPack) InstallPlan() (InstallPlan, error) {
	components, err := p.componentsWithLifecycleClosure()
	if err != nil {
		return InstallPlan{}, err
	}
	plan, err := NewInstallPlan(components)
	if err != nil {
		return InstallPlan{}, err
	}
	plan.packName = p.Name()
	return plan, nil
}

// lifecycleClosureComponents returns the synthetic convergence work that is
// intentionally excluded from declared-component identity and payload hashes,
// but must participate in every apply and verify lifecycle. Keeping this as one
// owner prevents full and delta install plans from silently disagreeing about
// cleanup and exact-set reconciliation.
func (p *SourceBackedPack) lifecycleClosureComponents() ([]PackComponent, error) {
	return p.lifecycleClosureComponentsWithProvenKinds(nil)
}

func (p *SourceBackedPack) lifecycleClosureComponentsWithProvenKinds(additionalProvenKinds map[ComponentKind]bool) ([]PackComponent, error) {
	components := append([]PackComponent(nil), p.sweepComponents...)
	managedSets, err := p.externalManagedSetComponentsWithProvenKinds(additionalProvenKinds)
	if err != nil {
		return nil, err
	}
	components = append(components, managedSets...)
	return components, nil
}

func (p *SourceBackedPack) componentsWithLifecycleClosure() ([]PackComponent, error) {
	components := append([]PackComponent(nil), p.components...)
	lifecycle, err := p.lifecycleClosureComponents()
	if err != nil {
		return nil, err
	}
	components = append(components, lifecycle...)
	return components, nil
}

func (p *SourceBackedPack) PayloadHash() (string, error) {
	return packComponentsPayloadHashV1(p, p.hashableComponents())
}

// LegacyPayloadHash keeps source-backed legacy-v0 hash validation on the same
// declared-component projection used by PayloadHash.
func (p *SourceBackedPack) LegacyPayloadHash() (string, error) {
	return legacyPackComponentsPayloadHashV0(p.hashableComponents())
}

// hashableComponents returns the manifest-declared components that participate
// in both v1 and legacy v0 payload hashing.
func (p *SourceBackedPack) hashableComponents() []PackComponent {
	return append([]PackComponent(nil), p.components...)
}

func (p *SourceBackedPack) PackDependencies() []PackDependency {
	return append([]PackDependency(nil), p.manifest.Dependencies...)
}

func (p *SourceBackedPack) HostRequirements() []HostPrimitiveRequirement {
	return append([]HostPrimitiveRequirement(nil), p.manifest.HostRequirements...)
}

func (p *SourceBackedPack) PackHashMetadata() PackHashMetadata {
	platformCompatibility, _ := json.Marshal(p.manifest.PlatformCompatibility)
	meta := PackHashMetadata{
		PackVersion:                strings.TrimSpace(p.manifest.Version),
		ManifestFormatVersion:      strings.TrimSpace(p.manifest.ManifestFormatVersion),
		PlatformCompatibility:      string(platformCompatibility),
		PlatformCompatibilityValid: true,
		PayloadHashAlgorithm:       PackPayloadHashAlgorithmV1,
		SourceKind:                 strings.TrimSpace(p.source.Kind),
		SourceURI:                  strings.TrimSpace(p.source.URI),
		SourceKindValid:            strings.TrimSpace(p.source.Kind) != "",
		SourceURIValid:             strings.TrimSpace(p.source.URI) != "",
	}
	if strings.TrimSpace(p.source.Revision) != "" {
		meta.SourceRevision = strings.TrimSpace(p.source.Revision)
		meta.SourceRevisionValid = true
	}
	return meta
}

func validateConnectorFieldMappingNoCrossComponentDuplicates(rows []connector.ConnectorFieldMappingInput) error {
	return connector.ValidateConnectorFieldMappingInputSet(rows)
}

var _ Pack = (*SourceBackedPack)(nil)
var _ PayloadHasher = (*SourceBackedPack)(nil)
var _ LegacyPayloadHasher = (*SourceBackedPack)(nil)
var _ PackHashMetadataProvider = (*SourceBackedPack)(nil)
var _ ComposablePack = (*SourceBackedPack)(nil)
var _ ComponentDeclarer = (*SourceBackedPack)(nil)
var _ PackDependencyProvider = (*SourceBackedPack)(nil)
var _ runtimeMetadataProvider = (*SourceBackedPack)(nil)
