package packs

import (
	"fmt"
	"os"
	"strings"

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

type packWriteMaterializedFormulaRef struct {
	ref           formula.WriteMaterializedFormulaRef
	businessKey   string
	componentKind string
	manageability string
	objectAPIName string
	fieldAPIName  string
}

func writeMaterializedFormulaRefsFromObjectSpecComponent(c *sourceBackedObjectSpecComponent) ([]packWriteMaterializedFormulaRef, error) {
	if c == nil {
		return nil, fmt.Errorf("nil object spec component")
	}
	if c.specs == nil && len(strings.TrimSpace(string(c.payload))) > 0 {
		return nil, fmt.Errorf("object spec payload was not parsed into specs")
	}

	schemas := c.packObjectSchemas
	if len(schemas) == 0 {
		schemas = formulaSchemasFromObjectSpecs(c.specs)
	}
	return writeMaterializedFormulaRefsFromObjectSpecs(
		string(c.Kind()),
		strings.TrimSpace(c.BusinessKey()),
		string(c.Manageability()),
		c.specs,
		schemas,
	), nil
}

func frozenFormulaRefsFromObjectSpecComponent(c *sourceBackedObjectSpecComponent) ([]formula.FrozenFormulaRef, error) {
	if c == nil {
		return nil, fmt.Errorf("nil object spec component")
	}
	if c.specs == nil && len(strings.TrimSpace(string(c.payload))) > 0 {
		return nil, fmt.Errorf("object spec payload was not parsed into specs")
	}

	schemas := c.packObjectSchemas
	if len(schemas) == 0 {
		schemas = formulaSchemasFromObjectSpecs(c.specs)
	}
	return frozenFormulaRefsFromObjectSpecs(
		strings.TrimSpace(c.packName),
		string(c.Kind()),
		strings.TrimSpace(c.BusinessKey()),
		string(c.Manageability()),
		c.specs,
		schemas,
	), nil
}

func writeMaterializedFormulaRefsFromObjectSpecs(componentKind, businessKey, manageability string, specs []ObjectSpec, schemas map[string]*models.ObjectMetadata) []packWriteMaterializedFormulaRef {
	if len(schemas) == 0 {
		schemas = formulaSchemasFromObjectSpecs(specs)
	}
	refs := make([]packWriteMaterializedFormulaRef, 0)
	for _, spec := range specs {
		objectAPIName := strings.TrimSpace(spec.APIName)
		for _, field := range spec.Fields {
			if strings.TrimSpace(field.Formula) == "" &&
				strings.TrimSpace(field.FormulaMode) == "" &&
				strings.TrimSpace(field.FormulaStorageMode) == "" &&
				field.ProjectionConfig == nil {
				continue
			}
			refs = append(refs, packWriteMaterializedFormulaRef{
				ref: formula.WriteMaterializedFormulaRef{
					FormulaMode:            strings.TrimSpace(field.FormulaMode),
					FormulaStorageMode:     strings.TrimSpace(field.FormulaStorageMode),
					Formula:                field.Formula,
					HasProjectionConfig:    field.ProjectionConfig != nil,
					Schema:                 schemas[objectAPIName],
					SchemasByObjectAPIName: schemas,
					ExpectedType:           formulaExpectedType(field),
				},
				componentKind: strings.TrimSpace(componentKind),
				businessKey:   strings.TrimSpace(businessKey),
				manageability: strings.TrimSpace(manageability),
				objectAPIName: objectAPIName,
				fieldAPIName:  strings.TrimSpace(field.APIName),
			})
		}
		// LockRule is validated by its own dedicated verifier (validateSpecLockRules /
		// ValidateLockRuleExpression) which accepts $User/$Profile/$UserRole globals.
		// Do NOT add it here; the generic frozen-formula verifier rejects those
		// session-global references and would break pack verify for valid LockRules.
	}
	return refs
}

func frozenFormulaRefsFromObjectSpecs(packName, componentKind, businessKey, manageability string, specs []ObjectSpec, schemas map[string]*models.ObjectMetadata) []formula.FrozenFormulaRef {
	if len(schemas) == 0 {
		schemas = formulaSchemasFromObjectSpecs(specs)
	}
	refs := make([]formula.FrozenFormulaRef, 0)
	for _, spec := range specs {
		objectAPIName := strings.TrimSpace(spec.APIName)
		for _, field := range spec.Fields {
			if strings.TrimSpace(field.Formula) == "" && strings.TrimSpace(field.FormulaMode) == "" {
				continue
			}
			refs = append(refs, formula.FrozenFormulaRef{
				Authority:              formula.FrozenFormulaAuthorityPackPayload,
				PackName:               strings.TrimSpace(packName),
				ComponentKind:          strings.TrimSpace(componentKind),
				BusinessKey:            strings.TrimSpace(businessKey),
				Manageability:          strings.TrimSpace(manageability),
				ObjectAPIName:          objectAPIName,
				FieldAPIName:           strings.TrimSpace(field.APIName),
				FormulaMode:            strings.TrimSpace(field.FormulaMode),
				Formula:                field.Formula,
				Schema:                 schemas[objectAPIName],
				SchemasByObjectAPIName: schemas,
				ExpectedType:           formulaExpectedType(field),
			})
		}
	}
	return refs
}

func formulaExpectedType(field FieldSpec) *models.FieldType {
	typeName := strings.TrimSpace(field.ReturnType)
	if typeName == "" {
		typeName = strings.TrimSpace(field.Type)
	}
	if typeName == "" {
		return nil
	}
	fieldType := models.FieldType(constants.NormalizeFieldType(typeName))
	return &fieldType
}

func formulaSchemasFromObjectSpecs(specs []ObjectSpec) map[string]*models.ObjectMetadata {
	stdFields := persistence.NewSchemaRepository(nil, nil).GetStandardFieldMetadata()
	customObjectStdFields := persistence.GetCustomObjectFieldMetadata()
	out := make(map[string]*models.ObjectMetadata, len(specs))
	for _, spec := range specs {
		objectAPIName := strings.TrimSpace(spec.APIName)
		if objectAPIName == "" {
			continue
		}
		injectedStdFields := stdFields
		if constants.TableType(spec.TableType) == constants.TableTypeCustomObject {
			injectedStdFields = customObjectStdFields
		}
		schema := &models.ObjectMetadata{
			APIName: objectAPIName,
			Fields:  append([]models.FieldMetadata(nil), injectedStdFields...),
		}
		for _, fSpec := range spec.Fields {
			fieldType := constants.SchemaFieldType(strings.TrimSpace(fSpec.Type))
			field := models.FieldMetadata{
				APIName:               strings.TrimSpace(fSpec.APIName),
				Label:                 strings.TrimSpace(fSpec.Label),
				Type:                  fieldType,
				Required:              packFieldRequired(fSpec),
				IsMasterDetail:        packFieldIsMasterDetail(fSpec),
				IsNameField:           fSpec.IsNameField,
				ReferenceTo:           append([]string(nil), fSpec.ReferenceTo...),
				WritableReferenceTo:   append([]string(nil), fSpec.WritableReferenceTo...),
				RelationshipName:      strPtr(strings.TrimSpace(fSpec.RelationshipName)),
				IsPolymorphic:         fSpec.isPolymorphicReference(),
				WriteProtectionPolicy: strings.TrimSpace(fSpec.WriteProtectionPolicy),
				ReadProtectionPolicy:  strings.TrimSpace(fSpec.ReadProtectionPolicy),
			}
			if strings.TrimSpace(fSpec.Formula) != "" {
				formulaExpr := fSpec.Formula
				field.Formula = &formulaExpr
			}
			if strings.TrimSpace(fSpec.FormulaMode) != "" {
				formulaMode := strings.TrimSpace(fSpec.FormulaMode)
				field.FormulaMode = &formulaMode
			}
			if strings.TrimSpace(fSpec.ReturnType) != "" {
				returnType := constants.SchemaFieldType(strings.TrimSpace(fSpec.ReturnType))
				field.ReturnType = &returnType
			}
			if fSpec.RollupConfig != nil {
				field.RollupConfig = cloneNormalizedRollupConfig(fSpec.RollupConfig)
			}
			schema.Fields = append(schema.Fields, field)
		}
		out[objectAPIName] = schema
	}
	return out
}

func frozenFormulaSchemasFromObjectSpecs(specs []ObjectSpec) map[string]*models.ObjectMetadata {
	return formulaSchemasFromObjectSpecs(specs)
}

func verifyWriteMaterializedFormulaPayloadForObjectSpecComponent(c *sourceBackedObjectSpecComponent, formulaFunctionBundles []formula.FunctionBundle) error {
	refs, err := writeMaterializedFormulaRefsFromObjectSpecComponent(c)
	if err != nil {
		return fmt.Errorf("enumerate pack payload write-materialized formulas: %w", err)
	}
	return verifyWriteMaterializedFormulaPayloadRefs(refs, formulaFunctionBundles)
}

func verifyWriteMaterializedFormulaPayloadRefs(refs []packWriteMaterializedFormulaRef, formulaFunctionBundles []formula.FunctionBundle) error {
	if os.Getenv("DISABLE_FORMULA_VALIDATION") == "true" {
		return fmt.Errorf("pack payload write-materialized-formula verification disabled by DISABLE_FORMULA_VALIDATION=true")
	}

	engine := formula.NewEngine(formula.WithoutRuntimeFunctionBundles())
	if len(formulaFunctionBundles) > 0 {
		if err := engine.RegisterFunctionBundles(formula.CloneFunctionBundles(formulaFunctionBundles)); err != nil {
			return fmt.Errorf("pack payload write-materialized-formula verification failed to load function bundles: %w", err)
		}
	}
	details := make([]string, 0, len(refs))
	for _, ref := range refs {
		if err := formula.ValidateWriteMaterializedFormulaRef(ref.ref, engine); err == nil {
			continue
		} else {
			details = append(details, fmt.Sprintf(
				"%s.%s (component %s, manageability %s): %s",
				ref.objectAPIName,
				ref.fieldAPIName,
				ref.businessKey,
				ref.manageability,
				err.Error(),
			))
		}
	}
	if len(details) == 0 {
		return nil
	}
	return fmt.Errorf("pack payload write-materialized-formula verification failed:\n  - %s", strings.Join(details, "\n  - "))
}

func verifyFrozenFormulaPayloadForObjectSpecComponent(c *sourceBackedObjectSpecComponent) error {
	refs, err := frozenFormulaRefsFromObjectSpecComponent(c)
	if err != nil {
		return fmt.Errorf("enumerate pack payload frozen formulas: %w", err)
	}
	return verifyFrozenFormulaPayloadRefs(refs)
}

func verifyFrozenFormulaPayloadRefs(refs []formula.FrozenFormulaRef) error {
	if os.Getenv("DISABLE_FORMULA_VALIDATION") == "true" {
		return fmt.Errorf("pack payload frozen-formula verification disabled by DISABLE_FORMULA_VALIDATION=true")
	}

	verdict, violations, err := formula.VerifyFrozenFormulaRefs(refs, formula.NewEngine())
	if err != nil {
		return fmt.Errorf("verify pack payload frozen formulas: %w", err)
	}
	if verdict == formula.Verified {
		return nil
	}
	if len(violations) == 0 {
		return fmt.Errorf("pack payload frozen-formula verification did not return Verified (got %s)", verdict)
	}

	details := make([]string, 0, len(violations))
	for _, violation := range violations {
		details = append(details, fmt.Sprintf(
			"%s.%s (component %s, manageability %s): %s",
			violation.ObjectAPIName,
			violation.FieldAPIName,
			violation.BusinessKey,
			violation.Manageability,
			violation.Reason,
		))
	}
	return fmt.Errorf("pack payload frozen-formula verification failed:\n  - %s", strings.Join(details, "\n  - "))
}
