package packs

import (
	"context"
	"fmt"
	"log"
	"strings"

	"github.com/nexuscrm/kernel/pkg/formula"
	domainschema "github.com/nexuscrm/kernel/pkg/schema"
	"github.com/nexuscrm/metadata"
	"github.com/nexuscrm/persistence"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-controlplane/specdrift"
)

type sourceBackedObjectSpecComponent struct {
	packName                    string
	registrations               *ComponentKindRegistrationSet
	manifestComponent           ManifestComponent
	payload                     []byte
	specs                       []ObjectSpec
	packObjectSpecs             []ObjectSpec
	packObjectSchemas           map[string]*models.ObjectMetadata
	fieldCompat                 packFieldAuthoringCompat
	allowOwnerlessObjectAPI     map[string]bool
	standaloneListFieldsObjects map[string]struct{}
	formulaFunctionBundles      []formula.FunctionBundle
}

type sourceBackedObjectSpecTarget struct {
	businessKey           string
	manageability         Manageability
	objectAPIName         string
	hasRawChildCloneRules bool
	hasRawRecordMutation  bool
	hasRawLifecycle       bool
}

func (c *sourceBackedObjectSpecComponent) Kind() ComponentKind {
	return ComponentKindObjectSpec
}

func (c *sourceBackedObjectSpecComponent) PackName() string {
	return c.packName
}

func (c *sourceBackedObjectSpecComponent) BusinessKey() string {
	return strings.TrimSpace(c.manifestComponent.BusinessKey)
}

func (c *sourceBackedObjectSpecComponent) Manageability() Manageability {
	return c.manifestComponent.Manageability
}

func (c *sourceBackedObjectSpecComponent) ExpectedPayload() ([]byte, error) {
	return append([]byte(nil), c.payload...), nil
}

func (c *sourceBackedObjectSpecComponent) ExpectedProjection() (any, error) {
	return clonePackComponentProjection(c.specs)
}

func (c *sourceBackedObjectSpecComponent) LiveProjection(context.Context, VerifyDeps) (any, error) {
	return nil, nil
}

func (c *sourceBackedObjectSpecComponent) DependsOn() []ComponentReference {
	return append([]ComponentReference(nil), c.manifestComponent.DependsOn...)
}

func (c *sourceBackedObjectSpecComponent) fieldResolver() objectSpecFieldResolver {
	return c.fieldResolverWithFallback(nil)
}

func (c *sourceBackedObjectSpecComponent) fieldResolverWithFallback(fallbackSchema func(string) *models.ObjectMetadata) objectSpecFieldResolver {
	specs := c.packObjectSpecs
	if len(specs) == 0 {
		specs = c.specs
	}
	return newObjectSpecFieldResolverWithFallback(specs, c.packObjectSchemas, fallbackSchema)
}

func (c *sourceBackedObjectSpecComponent) applyDefinitions(ctx context.Context, deps ApplyDeps) ([]domainschema.TableDefinition, objectSpecFieldResolver, error) {
	resolver := c.fieldResolverWithFallback(sourceBackedApplySchemaFallback(ctx, deps))
	defs, err := convertSpecToTableDefinitionsWithResolver(c.specs, deps.Schema, resolver)
	if err != nil {
		return nil, objectSpecFieldResolver{}, fmt.Errorf("convert spec to table definitions: %w", err)
	}
	return defs, resolver, nil
}

func (c *sourceBackedObjectSpecComponent) Apply(ctx context.Context, deps ApplyDeps) error {
	var err error
	ctx, err = preflightMetadataTransactionExecutor(ctx, deps, c.packName)
	if err != nil {
		return err
	}
	defs, resolver, err := c.applyDefinitions(ctx, deps)
	if err != nil {
		return err
	}
	if err := guardSourceBackedObjectSpecsWithComponentKindRegistrationSet(ctx, deps.DB, c.packName, defs, c.allowOwnerlessObjectAPI, c.registrations, newObjectSpecRequiredFieldIndex(c.specs)); err != nil {
		return err
	}
	if err := detectStalePackOwnedFields(deps.DB, c.packName, defs); err != nil {
		return err
	}
	if err := prepareObjectSpecChildCloneRulePackOwnershipForApplyWithOptions(ctx, deps.DB, c.packName, c.BusinessKey(), c.Manageability(), c.specs, childCloneRuleApplyOwnershipOptions{
		convergeSidecarlessEmptyDefaultsOnly: true,
	}); err != nil {
		return err
	}
	if err := prepareObjectSpecExclusiveSiblingFieldPackOwnershipForApply(ctx, deps.DB, c.packName, c.BusinessKey(), c.Manageability(), c.specs); err != nil {
		return err
	}
	childCloneRuleValidationSpecs := c.packObjectSpecs
	if len(childCloneRuleValidationSpecs) == 0 {
		childCloneRuleValidationSpecs = c.specs
	}
	if err := applyObjectSpecsWithResolverAndValidation(ctx, deps, c.packName, c.specs, c.manifestComponent.Manageability == ManageabilityDefaultsOnly, false, resolver, c.fieldCompat, c.formulaFunctionBundles, childCloneRuleValidationSpecs); err != nil {
		return err
	}
	// Same-pack replays are allowed to be no-op updates when the sidecar
	// already matches the desired ownership payload.
	if err := persistObjectSpecChildCloneRulePackOwnership(ctx, deps.DB, c.packName, c.BusinessKey(), c.Manageability(), c.specs, false); err != nil {
		return err
	}
	if err := persistObjectSpecExclusiveSiblingFieldPackOwnership(ctx, deps.DB, c.packName, c.BusinessKey(), c.Manageability(), c.specs, false); err != nil {
		return err
	}
	return nil
}

func (c *sourceBackedObjectSpecComponent) repairSubstrate(ctx context.Context, deps ApplyDeps) error {
	var err error
	ctx, err = preflightMetadataTransactionExecutor(ctx, deps, c.packName)
	if err != nil {
		return err
	}
	// Pre-repair: rebuild missing physical columns for still-declared
	// managed system fields from live _System_Field metadata. This must
	// happen before the full apply path so that bootstrap.ReconcileTableColumns
	// sees the table in a consistent state (column exists + metadata row
	// exists). Without this pre-step, the verify-before-repair gate sees
	// "physical column missing" drift and the full apply path's
	// ReconcileTableColumns can only add columns from the spec-derived
	// table definition, which is the same as what BuildManagedFieldColumnDefinition
	// produces for still-declared fields, but this explicit metadata-driven
	// path ensures convergence even when the spec-derived table definition
	// pipeline has a gap (e.g., managed system fields that are filtered out
	// of the spec-to-column conversion).
	if err := repairManagedSystemFieldColumnsFromLiveMetadata(ctx, deps.DB, c.packName, c.specs); err != nil {
		log.Printf("managed system field column pre-repair for %s: %v (continuing with full apply path)", c.BusinessKey(), err)
	}

	resolver := c.fieldResolverWithFallback(sourceBackedApplySchemaFallback(ctx, deps))
	if err := prepareObjectSpecChildCloneRulePackOwnershipForApplyWithOptions(ctx, deps.DB, c.packName, c.BusinessKey(), c.Manageability(), c.specs, childCloneRuleApplyOwnershipOptions{
		convergeSidecarlessEmptyDefaultsOnly: true,
	}); err != nil {
		return err
	}
	if err := prepareObjectSpecExclusiveSiblingFieldPackOwnershipForApply(ctx, deps.DB, c.packName, c.BusinessKey(), c.Manageability(), c.specs); err != nil {
		return err
	}
	childCloneRuleValidationSpecs := c.packObjectSpecs
	if len(childCloneRuleValidationSpecs) == 0 {
		childCloneRuleValidationSpecs = c.specs
	}
	if err := applyObjectSpecsWithResolverAndValidation(ctx, deps, c.packName, c.specs, c.manifestComponent.Manageability == ManageabilityDefaultsOnly, false, resolver, c.fieldCompat, c.formulaFunctionBundles, childCloneRuleValidationSpecs); err != nil {
		return err
	}
	if err := persistObjectSpecChildCloneRulePackOwnership(ctx, deps.DB, c.packName, c.BusinessKey(), c.Manageability(), c.specs, false); err != nil {
		return err
	}
	if err := persistObjectSpecExclusiveSiblingFieldPackOwnership(ctx, deps.DB, c.packName, c.BusinessKey(), c.Manageability(), c.specs, false); err != nil {
		return err
	}
	return nil
}

func sourceBackedObjectSpecsTouchSet(specs []ObjectSpec, objectSet map[string]bool) bool {
	for _, spec := range specs {
		if objectSet[strings.ToLower(strings.TrimSpace(spec.APIName))] {
			return true
		}
	}
	return false
}

func sourceBackedApplySchemaFallback(ctx context.Context, deps ApplyDeps) func(string) *models.ObjectMetadata {
	if deps.Metadata == nil {
		return nil
	}
	return func(apiName string) *models.ObjectMetadata {
		return deps.Metadata.GetSchema(ctx, apiName)
	}
}

// specdriftPolicyForManageability maps a component's L1-defaults-vs-L2-overrides
// manageability to the specdrift customization policy. Locked surfaces forbid
// IsCustomized=1 from masking structural drift; defaults_only surfaces accept
// it as legitimate admin ownership transfer.
func specdriftPolicyForManageability(m Manageability) specdrift.CustomizationPolicy {
	if m == ManageabilityDefaultsOnly {
		return specdrift.CustomizationPolicyAllow
	}
	return specdrift.CustomizationPolicyDrift
}

func verifyTableTypesForObjectSpecs(specs []ObjectSpec) []constants.TableType {
	tableTypes := []constants.TableType{constants.TableTypeStandardObject}
	for _, spec := range specs {
		if constants.TableType(spec.TableType) == constants.TableTypeCustomObject {
			tableTypes = append(tableTypes, constants.TableTypeCustomObject)
			break
		}
	}
	return tableTypes
}

func (c *sourceBackedObjectSpecComponent) VerifyComponent(ctx context.Context, deps VerifyDeps) error {
	if err := verifyWriteMaterializedFormulaPayloadForObjectSpecComponent(c, c.formulaFunctionBundles); err != nil {
		return err
	}
	if err := validateSpecReadSources(c.specs); err != nil {
		return fmt.Errorf("verify readSource contract: %w", err)
	}
	resolver := c.fieldResolverWithFallback(sourceBackedVerifySchemaFallback(ctx, deps))
	if err := validateSpecLockRulesWithFunctionBundles(c.specs, resolver, c.formulaFunctionBundles); err != nil {
		return fmt.Errorf("verify lock rules: %w", err)
	}
	// Issue #1110 / PR #1133: re-check displayNameField at verify time to
	// catch drift. A pack-spec edit that points displayNameField at a field
	// later removed, renamed, or retyped to a non-renderable type must
	// surface here rather than fail silently at frontend render time.
	if err := validateSpecDisplayNameFieldsWithResolver(c.specs, resolver); err != nil {
		return fmt.Errorf("verify display name fields: %w", err)
	}
	var errs []error
	// Customization policy follows the component's declared manageability.
	// `pack_component.go` records the per-kind enforcement reality, and
	// `docs/design/l0-platform-substrate.md` summarizes the Verify rule.
	// Both `locked` and `defaults_only` are accepted by the install gate
	// above; specdrift maps locked to Drift (any IsCustomized=1 row is
	// structural drift) and defaults_only to Allow (admin ownership transfers
	// preserve the row).
	policy := specdriftPolicyForManageability(c.manifestComponent.Manageability)
	if err := specdrift.VerifyForTableTypes(
		deps.DB,
		c.payload,
		fmt.Sprintf("source-backed:%s:%s", c.packName, c.BusinessKey()),
		nil,
		true,
		policy,
		verifyTableTypesForObjectSpecs(c.specs)...,
	); err != nil {
		errs = append(errs, err)
	}
	log.Printf("source-backed verify: %s finished spec drift", c.BusinessKey())
	defs, err := tableDefinitionsForObjectSpecsWithResolver(c.specs, resolver)
	if err != nil {
		errs = append(errs, err)
	} else {
		log.Printf("source-backed verify: %s verifying physical schema", c.BusinessKey())
		if err := verifyPackObjectPhysicalSchemaWithContextAndAuthoredFieldsAndComponentKindRegistrationSet(ctx, deps.DB, c.packName, defs, c.manifestComponent.Manageability, newObjectSpecAuthoredFieldSet(c.specs), c.registrations, newObjectSpecRequiredFieldIndex(c.specs)); err != nil {
			errs = append(errs, err)
		}
		log.Printf("source-backed verify: %s finished physical schema", c.BusinessKey())
	}
	log.Printf("source-backed verify: %s verifying custom setting authority", c.BusinessKey())
	if err := VerifyPackObjectCustomSetting(deps.DB, c.packName, c.specs, c.manifestComponent.Manageability); err != nil {
		errs = append(errs, err)
	}
	log.Printf("source-backed verify: %s finished custom setting authority", c.BusinessKey())
	log.Printf("source-backed verify: %s verifying default child rows", c.BusinessKey())
	if err := VerifyPackObjectDefaultChildRows(deps.DB, c.packName, c.specs, c.manifestComponent.Manageability); err != nil {
		errs = append(errs, err)
	}
	log.Printf("source-backed verify: %s finished default child rows", c.BusinessKey())
	log.Printf("source-backed verify: %s verifying list fields", c.BusinessKey())
	listFieldsSpecs := objectSpecsForListFieldsVerify(c.specs, c.manifestComponent.Manageability, c.standaloneListFieldsObjects)
	if err := VerifyPackObjectListFields(deps.DB, c.packName, listFieldsSpecs, c.manifestComponent.Manageability); err != nil {
		errs = append(errs, err)
	}
	log.Printf("source-backed verify: %s finished list fields", c.BusinessKey())
	log.Printf("source-backed verify: %s verifying child clone rules", c.BusinessKey())
	if err := VerifyPackObjectChildCloneRules(deps.DB, c.packName, c.BusinessKey(), c.specs, c.manifestComponent.Manageability); err != nil {
		errs = append(errs, err)
	}
	log.Printf("source-backed verify: %s finished child clone rules", c.BusinessKey())
	log.Printf("source-backed verify: %s verifying exclusive sibling fields", c.BusinessKey())
	if err := VerifyPackObjectExclusiveSiblingFields(deps.DB, c.packName, c.BusinessKey(), c.specs, c.manifestComponent.Manageability); err != nil {
		errs = append(errs, err)
	}
	log.Printf("source-backed verify: %s finished exclusive sibling fields", c.BusinessKey())
	if len(errs) > 0 {
		return &objectSpecVerifyError{errs: errs}
	}
	return nil
}

func sourceBackedVerifySchemaFallback(ctx context.Context, deps VerifyDeps) func(string) *models.ObjectMetadata {
	if deps.DB == nil {
		return nil
	}
	metadataRepo := persistence.NewMetadataRepository(deps.DB)
	schemaRepo := persistence.NewSchemaRepository(deps.DB, nil)
	metadataSvc := metadata.NewMetadataService(metadataRepo, metadata.NewSchemaManager(schemaRepo), nil)
	return func(apiName string) *models.ObjectMetadata {
		return metadataSvc.GetSchema(ctx, apiName)
	}
}
