package metadata

import (
	"context"
	"fmt"
	"strings"

	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	domainSchema "github.com/nexuscrm/kernel/pkg/schema"
	"github.com/nexuscrm/persistence"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

// --- DRY Helpers ---

// PrepareTableDefinition consolidates validation, enrichment, and mapping logic from ObjectMetadata to TableDefinition
// It returns the Physical TableDefinition and a slice of FieldWithContext for batch processing
func (ms *MetadataService) PrepareTableDefinition(ctx context.Context, schema *models.ObjectMetadata) (domainSchema.TableDefinition, []FieldWithContext, error) {
	if schema == nil {
		return domainSchema.TableDefinition{}, nil, apperrors.NewValidationError("api_name", "object metadata is required")
	}
	if err := validateCatalogObjectAPIName(schema.APIName); err != nil {
		return domainSchema.TableDefinition{}, nil, apperrors.NewValidationError("api_name", err.Error())
	}

	// Determine defaults
	if schema.Description == nil || *schema.Description == "" {
		desc := schema.Label
		schema.Description = &desc
	}
	// In pure meta-driven architecture, all non-system objects use custom_object table type
	tableType := constants.TableTypeCustomObject
	if schema.ID == "" {
		schema.ID = GenerateObjectID(schema.APIName)
	}

	// Ensure System Fields
	standardFields := persistence.GetCustomObjectFieldMetadata()
	EnrichWithSystemFields(schema, standardFields)
	if err := validateSiblingMaxFieldsForObject(schema, nil); err != nil {
		return domainSchema.TableDefinition{}, nil, err
	}

	// Build Table Definition
	def := domainSchema.TableDefinition{
		TableName:   schema.APIName,
		TableType:   string(tableType),
		Category:    constants.TableCategoryStandard,
		Description: *schema.Description,
		Columns:     make([]domainSchema.ColumnDefinition, 0),
	}

	var batchFields []FieldWithContext
	declaredFieldNames := make(map[string]bool, len(schema.Fields))
	discriminatorParents := make(map[string]string)
	for _, field := range schema.Fields {
		declaredFieldNames[strings.ToLower(field.APIName)] = true
		// Use the resolver (authored-write) so multi-target lookups and
		// references derive polymorphic ownership through the same SSoT.
		f := field
		if models.ResolveFieldRelationshipSemantics(&f, models.LifecycleAuthoredWrite).IsPolymorphic {
			discriminatorParents[strings.ToLower(domainSchema.PolymorphicDiscriminatorColumnName(field.APIName))] = field.APIName
		}
	}

	// Map Fields to Columns
	for i := range schema.Fields {
		field := &schema.Fields[i]
		// Logical and Physical names are identical by default (Custom Object logic handles suffixes later)
		colName := field.APIName

		// Update PhysicalName in metadata for persistence
		field.PhysicalName = colName

		sqlType, canonicalRollupConfig, err := ms.resolveFieldStorageType(ctx, schema.APIName, field)
		if err != nil {
			return domainSchema.TableDefinition{}, nil, apperrors.NewValidationError(field.APIName, err.Error())
		}
		if canonicalRollupConfig != nil {
			field.RollupConfig = canonicalRollupConfig
		}

		// Column Def
		colDef := domainSchema.ColumnDefinition{
			Name:                  colName,
			Type:                  sqlType,
			LogicalType:           string(field.Type),
			Nullable:              fieldPhysicalNullable(field),
			Unique:                field.IsUnique,
			Options:               field.Options,
			WritableReferenceTo:   append([]string(nil), field.WritableReferenceTo...),
			RollupConfig:          domainSchema.CloneRollupConfig(field.RollupConfig),
			WriteProtectionPolicy: field.WriteProtectionPolicy,
			ReadProtectionPolicy:  field.ReadProtectionPolicy,
			SiblingMaxParentField: siblingMaxColumnParentField(field),
		}

		// Special handling for ID -> Make it Primary Key
		if field.APIName == constants.FieldID {
			colDef.PrimaryKey = true
			colDef.Type = "VARCHAR(36)"
		}

		if field.DefaultValue != nil {
			colDef.Default = "'" + *field.DefaultValue + "'"
			if field.Type == constants.FieldTypeBoolean {
				if *field.DefaultValue == "false" || *field.DefaultValue == "0" {
					colDef.Default = "0"
				} else if *field.DefaultValue == "true" || *field.DefaultValue == "1" {
					colDef.Default = "1"
				}
			}
		}
		colDef.ReferenceTo = append([]string(nil), field.ReferenceTo...)
		// Resolve polymorphic ownership through the single SSoT. Normalize
		// the type so persisted multi-target lookup rows round-trip as
		// polymorphic after reload.
		if authoredSem := models.ResolveFieldRelationshipSemantics(field, models.LifecycleAuthoredWrite); authoredSem.IsPolymorphic {
			field.IsPolymorphic = true
			field.Type = authoredSem.CanonicalType
			colDef.IsPolymorphic = true
		}
		discriminatorParent := discriminatorParents[strings.ToLower(field.APIName)]
		if discriminatorParent != "" {
			field.GeneratedForPolymorphicReference = discriminatorParent
			field.Createable = false
			field.Updateable = false
			colDef.GeneratedForPolymorphicReference = discriminatorParent
		}
		def.Columns = append(def.Columns, colDef)

		// Field Metadata Context
		f := *field
		batchFields = append(batchFields, FieldWithContext{
			ObjectID:             schema.ID,
			FieldID:              GenerateFieldID(schema.APIName, f.APIName),
			Field:                &f,
			ColumnDefinition:     colDef,
			OwnerKind:            constants.FieldOwnerKindAdmin,
			CapabilitiesDeclared: discriminatorParent != "",
		})

		if field.IsPolymorphic {
			typeColDef := domainSchema.PolymorphicDiscriminatorColumn(colDef)
			if declaredFieldNames[strings.ToLower(typeColDef.Name)] {
				continue
			}
			typeField := models.FieldMetadata{
				APIName:                          typeColDef.Name,
				Label:                            typeColDef.Label,
				Type:                             constants.FieldTypeString,
				Createable:                       false,
				Updateable:                       false,
				GeneratedForPolymorphicReference: field.APIName,
			}
			def.Columns = append(def.Columns, typeColDef)
			batchFields = append(batchFields, FieldWithContext{
				ObjectID:             schema.ID,
				FieldID:              GenerateFieldID(schema.APIName, typeField.APIName),
				Field:                &typeField,
				ColumnDefinition:     typeColDef,
				OwnerKind:            constants.FieldOwnerKindAdmin,
				CapabilitiesDeclared: true,
			})
		}
	}

	if err := ms.validateRollupGraphForObject(ctx, schema); err != nil {
		return domainSchema.TableDefinition{}, nil, err
	}

	return def, batchFields, nil
}

const structuralProjectionSource = "structural_projection"

func structuralProjectionLayoutID(objectAPIName string) string {
	return fmt.Sprintf("%s-default-layout", objectAPIName)
}

// GenerateDefaultLayout creates an explicit L2/admin scaffold for a custom-object schema.
func (ms *MetadataService) GenerateDefaultLayout(schema *models.ObjectMetadata) models.PageLayout {
	return ms.buildStructuralDefaultLayout(schema, GenerateID(), constants.LayoutNameDefault, "admin_created")
}

// BuildStructuralLayoutProjection returns a non-persisted describe/layouts
// compatibility layout. It is deliberately record-type uniform and uses only
// structural field facts; L1/L2 page_layout metadata owns real sectioning.
func (ms *MetadataService) BuildStructuralLayoutProjection(ctx context.Context, schema *models.ObjectMetadata) *models.PageLayout {
	if schema == nil {
		return nil
	}
	layoutID := structuralProjectionLayoutID(schema.APIName)
	layout := ms.buildStructuralDefaultLayout(schema, layoutID, layoutID, structuralProjectionSource)
	ms.stripAutonumberSectionFields(ctx, &layout)
	return &layout
}

func (ms *MetadataService) buildStructuralDefaultLayout(schema *models.ObjectMetadata, layoutID, layoutName, source string) models.PageLayout {
	layout := models.PageLayout{
		ID:            layoutID,
		ObjectAPIName: schema.APIName,
		LayoutName:    layoutName,
		Type:          "Detail",
		CompactLayout: []string{}, // Populated below with first 7 structural business fields
		Sections: []models.PageSection{
			{
				ID:      GenerateID(),
				Label:   constants.SectionLabelInformation,
				Columns: 2,
				Fields:  []string{},
			},
			// NOTE: System Information section removed per Salesforce UI API standard.
			// System audit fields are auto-managed and not displayed in standard page layouts.
		},
		RelatedLists:  []models.RelatedListConfig{},
		HeaderActions: []models.ActionConfig{},
		QuickActions:  []models.ActionConfig{},
		Source:        source,
	}

	discriminatorFields := polymorphicDiscriminatorFieldNames(schema.Fields)
	for _, f := range schema.Fields {
		if discriminatorFields[strings.ToLower(f.APIName)] {
			continue
		}
		if !f.IsSystem && !isLayoutInvisibleField(f.APIName) {
			if !isAutoLayoutSectionExcluded(&f, nil) {
				layout.Sections[0].Fields = append(layout.Sections[0].Fields, f.APIName)
			}
			// Compact layout: first 7 structural business fields for highlights panel.
			if len(layout.CompactLayout) < 7 {
				layout.CompactLayout = append(layout.CompactLayout, f.APIName)
			}
		}
	}
	return layout
}

func polymorphicDiscriminatorFieldNames(fields []models.FieldMetadata) map[string]bool {
	out := make(map[string]bool)
	for _, f := range fields {
		// Use the resolver (authored-write) so multi-target lookups and
		// references derive polymorphic ownership through the same SSoT.
		fCopy := f
		if models.ResolveFieldRelationshipSemantics(&fCopy, models.LifecycleAuthoredWrite).IsPolymorphic {
			out[strings.ToLower(domainSchema.PolymorphicDiscriminatorColumnName(f.APIName))] = true
		}
	}
	return out
}
