package packs

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

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

func newSourceBackedSearchAllDefaultFieldsComponent(packName string, component ManifestComponent, payload []byte) (PackComponent, []searchmetadata.SearchAllDefaultFieldsDeclaration, error) {
	expected, err := searchAllDefaultFieldsProjection(payload, componentPayloadLabel(component))
	if err != nil {
		return nil, nil, err
	}
	return newSearchAllDefaultFieldsManagedComponentFromDeclarations(packName, component, payload, expected), expected, nil
}

// newSearchAllDefaultFieldsManagedComponentFromDeclarations builds the managed
// component from an already-parsed/validated declaration slice plus the RAW
// payload bytes and the engine ManifestComponent. It is shared by the internal
// build path (newSourceBackedSearchAllDefaultFieldsComponent) and the engine
// implementation of cp.PresentationComponentBuildContext.AddSearchAllDefaultFieldsComponent.
// The payload field is the raw bytes (never re-marshalled), and expectedProjection
// returns the parsed declaration slice so a deep-compare bites on content drift.
func newSearchAllDefaultFieldsManagedComponentFromDeclarations(packName string, component ManifestComponent, payload []byte, declarations []searchmetadata.SearchAllDefaultFieldsDeclaration) PackComponent {
	expected := append([]searchmetadata.SearchAllDefaultFieldsDeclaration(nil), declarations...)
	return &sourceBackedManagedComponent{
		packName:          packName,
		manifestComponent: component,
		payload:           payload,
		expectedProjection: func() (any, error) {
			return expected, nil
		},
		liveProjection: func(_ context.Context, _ VerifyDeps) (any, error) {
			return expected, nil
		},
		apply: func(_ context.Context, _ ApplyDeps) error {
			return nil
		},
	}
}

func searchAllDefaultFieldsProjection(payload []byte, source string) ([]searchmetadata.SearchAllDefaultFieldsDeclaration, error) {
	var items []searchmetadata.SearchAllDefaultFieldsDeclaration
	if err := json.Unmarshal(payload, &items); err != nil {
		return nil, fmt.Errorf("parse %s: %w", source, err)
	}
	if err := searchmetadata.ValidatePackSearchAllDefaultFields(items); err != nil {
		return nil, err
	}
	return items, nil
}

func rejectObjectSpecSearchAllDefaultFieldsPayload(payload []byte, source string) error {
	var specs []map[string]json.RawMessage
	if err := json.Unmarshal(payload, &specs); err != nil {
		return fmt.Errorf("parse %s: %w", source, err)
	}
	for _, spec := range specs {
		if _, ok := spec["searchAllDefaultFields"]; !ok {
			continue
		}
		objectName := "<unknown>"
		if rawName := spec["apiName"]; len(rawName) > 0 {
			var parsedName string
			if err := json.Unmarshal(rawName, &parsedName); err == nil && strings.TrimSpace(parsedName) != "" {
				objectName = strings.TrimSpace(parsedName)
			}
		}
		return fmt.Errorf("object_spec %s object %s must not declare searchAllDefaultFields; use the search_all_default_fields component", source, objectName)
	}
	return nil
}

func validateSearchAllDefaultFieldsForObjectSpecs(declarations []searchmetadata.SearchAllDefaultFieldsDeclaration, specs []ObjectSpec) error {
	if len(declarations) == 0 {
		return nil
	}
	byObject := make(map[string]*models.ObjectMetadata, len(specs))
	for _, spec := range specs {
		objectName := strings.TrimSpace(spec.APIName)
		if objectName == "" {
			continue
		}
		byObject[strings.ToLower(objectName)] = searchAllDefaultFieldsSchemaFromSpec(spec)
	}
	for _, decl := range declarations {
		objectName := strings.TrimSpace(decl.ObjectAPIName)
		schema := byObject[strings.ToLower(objectName)]
		if schema == nil {
			return fmt.Errorf("search_all_default_fields object %s is not declared by object_spec", objectName)
		}
		if _, err := searchmetadata.ResolveDeclaredSearchAllDefaultFieldsForSchema(schema, decl.Fields); err != nil {
			return fmt.Errorf("search_all_default_fields %s: %w", objectName, err)
		}
	}
	return nil
}

func validateSearchAllDefaultFieldsFallbackCoverageForObjectSpecs(declarations []searchmetadata.SearchAllDefaultFieldsDeclaration, specs []ObjectSpec) error {
	declared := make(map[string]struct{}, len(declarations))
	for _, decl := range declarations {
		objectName := strings.TrimSpace(decl.ObjectAPIName)
		if objectName == "" {
			continue
		}
		declared[strings.ToLower(objectName)] = struct{}{}
	}
	for _, spec := range specs {
		objectName := strings.TrimSpace(spec.APIName)
		if objectName == "" {
			continue
		}
		if _, ok := declared[strings.ToLower(objectName)]; ok {
			continue
		}
		if err := searchmetadata.ValidateLegacyAllSearchFieldsWithinFallbackCap(searchAllDefaultFieldsSchemaFromSpec(spec)); err != nil {
			return fmt.Errorf("search_all_default_fields %s: %w", objectName, err)
		}
	}
	return nil
}

func searchAllDefaultFieldsSchemaFromSpec(spec ObjectSpec) *models.ObjectMetadata {
	fields := make([]models.FieldMetadata, 0, len(spec.Fields)+8)
	fields = append(fields, persistence.ManagedObjectSystemFieldMetadata(spec.APIName, constants.TableType(spec.TableType))...)
	for _, fieldSpec := range spec.Fields {
		field := models.FieldMetadata{
			APIName:      fieldSpec.APIName,
			Label:        fieldSpec.Label,
			Type:         constants.SchemaFieldType(fieldSpec.Type),
			IsNameField:  fieldSpec.IsNameField,
			IsCompound:   fieldSpec.IsCompound,
			ComposedFrom: append([]string(nil), fieldSpec.ComposedFrom...),
		}
		if fieldSpec.ReturnType != "" {
			returnType := constants.SchemaFieldType(fieldSpec.ReturnType)
			field.ReturnType = &returnType
		}
		if fieldSpec.Formula != "" {
			formula := fieldSpec.Formula
			field.Formula = &formula
		}
		fields = append(fields, field)
	}
	return &models.ObjectMetadata{
		APIName:    spec.APIName,
		ListFields: append([]string(nil), spec.ListFields...),
		Fields:     fields,
	}
}

func cloneSearchAllDefaultFieldsDeclarations(declarations []searchmetadata.SearchAllDefaultFieldsDeclaration) []searchmetadata.SearchAllDefaultFieldsDeclaration {
	if len(declarations) == 0 {
		return nil
	}
	out := make([]searchmetadata.SearchAllDefaultFieldsDeclaration, 0, len(declarations))
	for _, decl := range declarations {
		out = append(out, searchmetadata.SearchAllDefaultFieldsDeclaration{
			ObjectAPIName: strings.TrimSpace(decl.ObjectAPIName),
			Fields:        append([]string(nil), decl.Fields...),
		})
	}
	return out
}
