package metadata

import (
	"context"
	"strings"

	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

// ServerDefaultedFieldsDecorator marks platform-owned fields whose values
// the server synthesizes when the API caller omits them. Salesforce v63
// describe surfaces this as the `defaultedOnCreate` attribute, which is
// independent of `required` (caller obligation) and `nillable` (storage
// admits NULL).
//
// Surface — narrowed strictly to fields whose deferred-supply contract is
// registered by platform/product composition or declared by first-class
// NexusCRM metadata:
//
//   - Any field with a non-blank `SiblingMaxParentField` declaration. The
//     records layer computes the concrete MAX+1 value transactionally at
//     create time when the caller omits the target field; the decorator only
//     projects that server-default contract to REST required-field validation
//     and SObject Describe.
//
// Notably NOT marked:
//
//   - User.Username — SF v63 requires Username on insert; no server-side
//     defaulting. `applyUserDefaults` carries a "Username = Email"
//     fallback for internal callers (BulkInsert, system-context) where
//     `validateRequiredForCreate` does not run, but that is a NexusCRM
//     internal convenience, not a public-API contract. Advertising it
//     via the decorator would be a SF v63 parity delta. Issue #1425
//     round-6 review.
//
// The decorator augments the in-memory schema only. The
// `models.CloneWithAdditionalFields` helper guarantees the rest of the
// `FieldMetadata` (Required, Type, Createable, Nillable-derivation inputs,
// etc.) survives unchanged — only `DefaultedOnCreate` flips.
//
// Consumers honor the flag:
//
//   - `validateRequiredForCreate` (REST handler, `sobject_records.go`)
//     skips fields with `DefaultedOnCreate=true`, so the caller is not
//     rejected with `REQUIRED_FIELD_MISSING` even though the field is
//     declared `Required=1`. The defaulter inside `Persistence.Insert`
//     then synthesizes the value before SQL writes.
//
//   - `sobject_describe.go` ORs the explicit flag with its existing
//     `f.DefaultValue != nil` derivation, so describe reports the
//     correct SF v63 contract to API clients.
//
// Other consumers of `FieldMetadata.Required` (UI form builder, edit-path
// validators, describe `nillable`) are intentionally untouched: the field is
// still semantically required on update and still backed by a `NOT NULL`
// storage column. The new flag is strictly a create-path skip signal.
type ServerDefaultedFieldsDecorator struct {
	declarations []ServerDefaultedFieldDeclaration
}

// ServerDefaultedFieldDeclaration declares an object field whose create-time
// server default is supplied by platform/product composition.
type ServerDefaultedFieldDeclaration struct {
	ObjectAPIName string
	FieldAPIName  string
}

func NewServerDefaultedFieldsDecorator(decls ...ServerDefaultedFieldDeclaration) *ServerDefaultedFieldsDecorator {
	return &ServerDefaultedFieldsDecorator{declarations: cloneServerDefaultedFieldDeclarations(decls)}
}

// PlatformServerDefaultedFieldDeclarations returns platform-floor server
// default declarations. User.Password is an auth floor field documented in
// docs/design/scope-floor.md: the records layer synthesizes a placeholder so
// admin-created users can enter the reset flow.
func PlatformServerDefaultedFieldDeclarations() []ServerDefaultedFieldDeclaration {
	return []ServerDefaultedFieldDeclaration{{
		ObjectAPIName: constants.TableUser,
		FieldAPIName:  constants.FieldPassword,
	}}
}

func serverDefaultedFieldsForSchema(schema *models.ObjectMetadata, decls []ServerDefaultedFieldDeclaration) []string {
	if schema == nil {
		return nil
	}
	seen := make(map[string]struct{}, len(decls)+len(schema.Fields))
	out := make([]string, 0, len(decls)+len(schema.Fields))
	for _, decl := range decls {
		if !strings.EqualFold(strings.TrimSpace(decl.ObjectAPIName), schema.APIName) {
			continue
		}
		apiName := strings.TrimSpace(decl.FieldAPIName)
		key := strings.ToLower(apiName)
		if key == "" {
			continue
		}
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, apiName)
	}
	for i := range schema.Fields {
		parentField := ""
		if schema.Fields[i].SiblingMaxParentField != nil {
			parentField = strings.TrimSpace(*schema.Fields[i].SiblingMaxParentField)
		}
		if parentField == "" {
			continue
		}
		key := strings.ToLower(schema.Fields[i].APIName)
		if key == "" {
			continue
		}
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, schema.Fields[i].APIName)
	}
	return out
}

func (d *ServerDefaultedFieldsDecorator) DecorateSchema(_ context.Context, schema *models.ObjectMetadata) (*models.ObjectMetadata, error) {
	if schema == nil {
		return nil, nil
	}
	candidates := serverDefaultedFieldsForSchema(schema, d.declarations)
	if len(candidates) == 0 {
		return schema, nil
	}

	replacements := make([]models.FieldMetadata, 0, len(candidates))
	for _, apiName := range candidates {
		existing := models.FindField(schema, apiName)
		if existing == nil {
			// Field not in the schema — nothing to decorate. Greenfield
			// always has these on User, but a partially-bootstrapped
			// schema mid-init may not. Skip silently rather than fail
			// the cache build (mirrors CompoundVirtualFieldDecorator).
			continue
		}
		if existing.DefaultedOnCreate {
			// Already marked — no-op for idempotency on repeated cache
			// builds (decorators run every refresh).
			continue
		}
		modified := *existing
		modified.DefaultedOnCreate = true
		replacements = append(replacements, modified)
	}
	if len(replacements) == 0 {
		return schema, nil
	}
	return models.CloneWithAdditionalFields(schema, replacements), nil
}

func cloneServerDefaultedFieldDeclarations(in []ServerDefaultedFieldDeclaration) []ServerDefaultedFieldDeclaration {
	if len(in) == 0 {
		return nil
	}
	out := make([]ServerDefaultedFieldDeclaration, len(in))
	copy(out, in)
	return out
}

// AutoNumberDefaultedFieldsDecorator marks fields backed by the autonumber
// engine as defaultedOnCreate in the same effective schema cache that describe
// uses. This keeps create-required validation and v63 describe on one read path:
// both consume FieldMetadata.DefaultedOnCreate rather than a side-channel list.
type AutoNumberDefaultedFieldsDecorator struct {
	resolve AutoNumberFieldResolver
}

func NewAutoNumberDefaultedFieldsDecorator(resolve AutoNumberFieldResolver) *AutoNumberDefaultedFieldsDecorator {
	return &AutoNumberDefaultedFieldsDecorator{resolve: resolve}
}

func (d *AutoNumberDefaultedFieldsDecorator) DecorateSchema(_ context.Context, schema *models.ObjectMetadata) (*models.ObjectMetadata, error) {
	if d == nil || d.resolve == nil || schema == nil {
		return schema, nil
	}
	fields := d.resolve(schema.APIName)
	if len(fields) == 0 {
		return schema, nil
	}
	replacements := make([]models.FieldMetadata, 0, len(fields))
	for apiName := range fields {
		apiName = strings.TrimSpace(apiName)
		if apiName == "" {
			continue
		}
		existing := models.FindField(schema, apiName)
		if existing == nil || existing.DefaultedOnCreate {
			continue
		}
		modified := *existing
		modified.DefaultedOnCreate = true
		replacements = append(replacements, modified)
	}
	if len(replacements) == 0 {
		return schema, nil
	}
	return models.CloneWithAdditionalFields(schema, replacements), nil
}
