package packs

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"sort"
	"strings"

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

// ValidateObjectExclusiveSiblingFields walks every ObjectMetadata
// ExclusiveSiblingFields declaration and verifies the contract is executable
// by the generic records runtime.
func ValidateObjectExclusiveSiblingFields(ctx context.Context, meta schemaLookup, schemas []*models.ObjectMetadata) error {
	var errs []string
	for _, obj := range schemas {
		if obj == nil || len(obj.ExclusiveSiblingFields) == 0 {
			continue
		}
		errs = append(errs, validateExclusiveSiblingFieldDuplicates(obj)...)
		for i, rule := range obj.ExclusiveSiblingFields {
			for _, msg := range validateExclusiveSiblingField(ctx, meta, obj, i, rule) {
				errs = append(errs, msg)
			}
		}
	}
	if len(errs) == 0 {
		return nil
	}
	return fmt.Errorf("ExclusiveSiblingFields validation failed:\n  - %s", strings.Join(errs, "\n  - "))
}

func ValidateExclusiveSiblingFieldsForSpecs(ctx context.Context, specs []ObjectSpec, fallback records.DefaultChildRowsSchemaSnapshot, allowUnresolvedExternal bool) error {
	return ValidateExclusiveSiblingFieldsForSpecsWithSnapshot(ctx, specs, specs, fallback, allowUnresolvedExternal)
}

func ValidateExclusiveSiblingFieldsForSpecsWithSnapshot(ctx context.Context, specs []ObjectSpec, snapshotSpecs []ObjectSpec, fallback records.DefaultChildRowsSchemaSnapshot, allowUnresolvedExternal bool) error {
	if len(specs) == 0 {
		return nil
	}
	if len(snapshotSpecs) == 0 {
		snapshotSpecs = specs
	}
	if err := validateExclusiveSiblingFieldSingleObjectSpecOwnership(specs, snapshotSpecs); err != nil {
		return fmt.Errorf("ExclusiveSiblingFields validation failed:\n  - %s", err)
	}
	snapshot := NewObjectMetadataSnapshot(snapshotSpecs, fallback)
	var errs []string
	for _, obj := range buildObjectMetadata(specs) {
		if obj == nil || len(obj.ExclusiveSiblingFields) == 0 {
			continue
		}
		errs = append(errs, validateExclusiveSiblingFieldDuplicates(obj)...)
		for i, rule := range obj.ExclusiveSiblingFields {
			for _, msg := range validateExclusiveSiblingField(ctx, snapshot, obj, i, rule) {
				if allowUnresolvedExternal && fallback == nil && isUnresolvedExternalExclusiveSiblingFieldMessage(msg, rule) {
					continue
				}
				errs = append(errs, msg)
			}
		}
	}
	if len(errs) == 0 {
		return nil
	}
	return fmt.Errorf("ExclusiveSiblingFields validation failed:\n  - %s", strings.Join(errs, "\n  - "))
}

func validateInstallableSourceBackedExclusiveSiblingFields(source *ResolvedPackSource) []PackDiagnostic {
	if source == nil || source.Manifest == nil {
		return nil
	}
	parsedObjectSpecs := make(map[string][]ObjectSpec)
	for _, component := range source.Manifest.Components {
		if component.Kind != ComponentKindObjectSpec {
			continue
		}
		payload, err := sourceBackedComponentPayload(source, component)
		p := componentPayloadLabel(component)
		if err != nil {
			return []PackDiagnostic{diag("component.payload.unavailable", p, err.Error())}
		}
		specs, err := parseObjectSpecsJSON(payload, componentPayloadLabel(component))
		if err != nil {
			return []PackDiagnostic{diag("component.object_spec.invalid_json", p, err.Error())}
		}
		businessKey := strings.TrimSpace(component.BusinessKey)
		parsedObjectSpecs[businessKey] = specs
	}
	if len(parsedObjectSpecs) == 0 {
		return nil
	}
	var allSpecs []ObjectSpec
	keys := make([]string, 0, len(parsedObjectSpecs))
	for key := range parsedObjectSpecs {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	for _, key := range keys {
		allSpecs = append(allSpecs, parsedObjectSpecs[key]...)
	}
	if err := validateSourceBackedExclusiveSiblingFieldDuplicateDeclarations(parsedObjectSpecs); err != nil {
		return []PackDiagnostic{diag("component.object_spec.duplicate_exclusive_sibling_fields", "object_spec", err.Error())}
	}
	if err := validateSourceBackedExclusiveSiblingFieldSingleObjectSpecOwnership(parsedObjectSpecs); err != nil {
		return []PackDiagnostic{diag("component.object_spec.exclusive_sibling_fields_split_object", "object_spec", err.Error())}
	}
	if err := ValidateExclusiveSiblingFieldsForSpecs(context.Background(), allSpecs, nil, true); err != nil {
		return []PackDiagnostic{diag("component.object_spec.invalid_exclusive_sibling_fields", "object_spec", err.Error())}
	}
	return nil
}

type exclusiveSiblingFieldSourceDeclaration struct {
	businessKey   string
	objectAPIName string
	specIndex     int
	ruleIndex     int
	fieldName     string
	partitionName string
}

type exclusiveSiblingFieldObjectSpecOccurrence struct {
	specIndex int
}

func validateExclusiveSiblingFieldSingleObjectSpecOwnership(specs []ObjectSpec, snapshotSpecs []ObjectSpec) error {
	if len(specs) == 0 {
		return nil
	}
	if len(snapshotSpecs) == 0 {
		snapshotSpecs = specs
	}
	occurrences := make(map[string][]exclusiveSiblingFieldObjectSpecOccurrence)
	for specIndex, spec := range snapshotSpecs {
		objectAPIName := strings.TrimSpace(spec.APIName)
		if objectAPIName == "" {
			continue
		}
		key := strings.ToLower(objectAPIName)
		occurrences[key] = append(occurrences[key], exclusiveSiblingFieldObjectSpecOccurrence{
			specIndex: specIndex,
		})
	}

	var errs []string
	reported := map[string]bool{}
	for _, spec := range specs {
		objectAPIName := strings.TrimSpace(spec.APIName)
		if objectAPIName == "" || len(spec.ExclusiveSiblingFields) == 0 {
			continue
		}
		key := strings.ToLower(objectAPIName)
		if reported[key] {
			continue
		}
		matches := occurrences[key]
		if len(matches) <= 1 {
			continue
		}
		specIndexes := make([]string, 0, len(matches))
		for _, occurrence := range matches {
			specIndexes = append(specIndexes, fmt.Sprintf("spec[%d]", occurrence.specIndex))
		}
		errs = append(errs, fmt.Sprintf(
			"object %s declares ExclusiveSiblingFields but is authored by %d object_spec specs (%s); ExclusiveSiblingFields is object-level metadata and must be declared only when the object has a single object_spec authoring surface",
			objectAPIName,
			len(matches),
			strings.Join(specIndexes, ", "),
		))
		reported[key] = true
	}
	if len(errs) == 0 {
		return nil
	}
	return fmt.Errorf("%s", strings.Join(errs, "\n  - "))
}

type exclusiveSiblingFieldSourceObjectSpecOccurrence struct {
	businessKey string
	specIndex   int
}

func validateSourceBackedExclusiveSiblingFieldSingleObjectSpecOwnership(specsByBusinessKey map[string][]ObjectSpec) error {
	occurrences := map[string][]exclusiveSiblingFieldSourceObjectSpecOccurrence{}
	keys := make([]string, 0, len(specsByBusinessKey))
	for key := range specsByBusinessKey {
		keys = append(keys, key)
	}
	sort.Strings(keys)

	for _, businessKey := range keys {
		specs := specsByBusinessKey[businessKey]
		for specIndex, spec := range specs {
			objectAPIName := strings.TrimSpace(spec.APIName)
			if objectAPIName == "" {
				continue
			}
			key := strings.ToLower(objectAPIName)
			occurrences[key] = append(occurrences[key], exclusiveSiblingFieldSourceObjectSpecOccurrence{
				businessKey: businessKey,
				specIndex:   specIndex,
			})
		}
	}

	var errs []string
	reported := map[string]bool{}
	for _, businessKey := range keys {
		specs := specsByBusinessKey[businessKey]
		for _, spec := range specs {
			objectAPIName := strings.TrimSpace(spec.APIName)
			if objectAPIName == "" || len(spec.ExclusiveSiblingFields) == 0 {
				continue
			}
			key := strings.ToLower(objectAPIName)
			if reported[key] {
				continue
			}
			matches := occurrences[key]
			if len(matches) <= 1 {
				continue
			}
			labels := make([]string, 0, len(matches))
			for _, occurrence := range matches {
				labels = append(labels, fmt.Sprintf("%q spec[%d]", occurrence.businessKey, occurrence.specIndex))
			}
			errs = append(errs, fmt.Sprintf(
				"object %s declares ExclusiveSiblingFields in object_spec %q but is authored by %d object_spec components/specs (%s); ExclusiveSiblingFields is object-level metadata and must use one object_spec authoring surface",
				objectAPIName,
				businessKey,
				len(matches),
				strings.Join(labels, ", "),
			))
			reported[key] = true
		}
	}
	if len(errs) == 0 {
		return nil
	}
	return fmt.Errorf("%s", strings.Join(errs, "\n  - "))
}

func validateSourceBackedExclusiveSiblingFieldDuplicateDeclarations(specsByBusinessKey map[string][]ObjectSpec) error {
	seen := map[string]exclusiveSiblingFieldSourceDeclaration{}
	keys := make([]string, 0, len(specsByBusinessKey))
	for key := range specsByBusinessKey {
		keys = append(keys, key)
	}
	sort.Strings(keys)

	var errs []string
	for _, businessKey := range keys {
		specs := specsByBusinessKey[businessKey]
		for specIndex, spec := range specs {
			objectAPIName := strings.TrimSpace(spec.APIName)
			if objectAPIName == "" {
				continue
			}
			for ruleIndex, rule := range spec.ExclusiveSiblingFields {
				fieldName := strings.TrimSpace(rule.FieldAPIName)
				partitionName := strings.TrimSpace(rule.PartitionFieldAPIName)
				if fieldName == "" || partitionName == "" {
					continue
				}
				key := strings.ToLower(objectAPIName) + "\x00" + strings.ToLower(fieldName) + "\x00" + strings.ToLower(partitionName)
				current := exclusiveSiblingFieldSourceDeclaration{
					businessKey:   businessKey,
					objectAPIName: objectAPIName,
					specIndex:     specIndex,
					ruleIndex:     ruleIndex,
					fieldName:     fieldName,
					partitionName: partitionName,
				}
				if first, ok := seen[key]; ok {
					errs = append(errs, fmt.Sprintf(
						"object %s ExclusiveSiblingFields[%d] in object_spec %q spec[%d] duplicates object_spec %q spec[%d] ExclusiveSiblingFields[%d] for fieldApiName %q and partitionFieldApiName %q",
						current.objectAPIName,
						current.ruleIndex,
						current.businessKey,
						current.specIndex,
						first.businessKey,
						first.specIndex,
						first.ruleIndex,
						current.fieldName,
						current.partitionName,
					))
					continue
				}
				seen[key] = current
			}
		}
	}
	if len(errs) == 0 {
		return nil
	}
	return fmt.Errorf("%s", strings.Join(errs, "\n  - "))
}

func validateExclusiveSiblingFieldDuplicates(obj *models.ObjectMetadata) []string {
	if obj == nil || len(obj.ExclusiveSiblingFields) == 0 {
		return nil
	}
	seen := map[string]int{}
	var out []string
	for i, rule := range obj.ExclusiveSiblingFields {
		fieldName := strings.TrimSpace(rule.FieldAPIName)
		partitionName := strings.TrimSpace(rule.PartitionFieldAPIName)
		if fieldName == "" || partitionName == "" {
			continue
		}
		key := strings.ToLower(fieldName) + "\x00" + strings.ToLower(partitionName)
		if first, ok := seen[key]; ok {
			out = append(out, fmt.Sprintf("%s.ExclusiveSiblingFields[%d]: duplicates %s.ExclusiveSiblingFields[%d] for fieldApiName %q and partitionFieldApiName %q", obj.APIName, i, obj.APIName, first, fieldName, partitionName))
			continue
		}
		seen[key] = i
	}
	return out
}

func validateExclusiveSiblingField(ctx context.Context, meta schemaLookup, obj *models.ObjectMetadata, idx int, rule models.ExclusiveSiblingField) []string {
	prefix := fmt.Sprintf("%s.ExclusiveSiblingFields[%d]", obj.APIName, idx)
	var out []string

	fieldName := strings.TrimSpace(rule.FieldAPIName)
	partitionName := strings.TrimSpace(rule.PartitionFieldAPIName)
	if fieldName == "" {
		out = append(out, fmt.Sprintf("%s: fieldApiName is required", prefix))
	}
	if partitionName == "" {
		out = append(out, fmt.Sprintf("%s: partitionFieldApiName is required", prefix))
	}
	if fieldName != "" && partitionName != "" && strings.EqualFold(fieldName, partitionName) {
		out = append(out, fmt.Sprintf("%s: fieldApiName and partitionFieldApiName must be different fields", prefix))
	}

	field := models.FindField(obj, fieldName)
	if fieldName != "" && field == nil {
		out = append(out, fmt.Sprintf("%s: fieldApiName %q does not exist on %s", prefix, fieldName, obj.APIName))
	} else if field != nil {
		if field.IsVirtual || field.Formula != nil || field.ProjectionConfig != nil {
			out = append(out, fmt.Sprintf("%s: fieldApiName %q must be a physical boolean field", prefix, field.APIName))
		}
		if constants.NormalizeFieldType(string(field.Type)) != constants.FieldTypeBoolean {
			out = append(out, fmt.Sprintf("%s: fieldApiName %q must be boolean, got %q", prefix, field.APIName, field.Type))
		}
		if !field.Createable && !field.Updateable {
			out = append(out, fmt.Sprintf("%s: fieldApiName %q must be writable on create or update", prefix, field.APIName))
		}
	}

	partitionField := models.FindField(obj, partitionName)
	if partitionName != "" && partitionField == nil {
		out = append(out, fmt.Sprintf("%s: partitionFieldApiName %q does not exist on %s", prefix, partitionName, obj.APIName))
	} else if partitionField != nil {
		sem := models.ResolveFieldRelationshipSemantics(partitionField, models.LifecycleAuthoredWrite)
		if !sem.IsRelationship {
			out = append(out, fmt.Sprintf("%s: partitionFieldApiName %q must be a reference or master-detail field", prefix, partitionField.APIName))
		}
		if sem.IsPolymorphic || len(sem.ReferenceTargets) != 1 || strings.TrimSpace(sem.ReferenceTargets[0]) == "" {
			out = append(out, fmt.Sprintf("%s: partitionFieldApiName %q must reference exactly one parent object", prefix, partitionField.APIName))
		} else if meta != nil && meta.GetSchema(ctx, sem.ReferenceTargets[0]) == nil {
			out = append(out, fmt.Sprintf("%s: partition parent object %q not found in metadata", prefix, sem.ReferenceTargets[0]))
		}
		if partitionField.IsVirtual || partitionField.Formula != nil || partitionField.ProjectionConfig != nil {
			out = append(out, fmt.Sprintf("%s: partitionFieldApiName %q must be a physical reference field", prefix, partitionField.APIName))
		}
	}
	return out
}

func isUnresolvedExternalExclusiveSiblingFieldMessage(msg string, _ models.ExclusiveSiblingField) bool {
	return strings.Contains(msg, "partition parent object ")
}

func canonicalExclusiveSiblingFieldsJSON(raw sql.NullString) ([]byte, error) {
	if !raw.Valid || strings.TrimSpace(raw.String) == "" {
		return canonicalExclusiveSiblingFields(nil)
	}
	var fields []models.ExclusiveSiblingField
	if err := json.Unmarshal([]byte(raw.String), &fields); err != nil {
		return nil, err
	}
	return canonicalExclusiveSiblingFields(fields)
}

func canonicalExclusiveSiblingFields(fields []models.ExclusiveSiblingField) ([]byte, error) {
	if len(fields) == 0 {
		return []byte("[]"), nil
	}
	type normalizedField struct {
		FieldAPIName          string `json:"fieldApiName"`
		PartitionFieldAPIName string `json:"partitionFieldApiName"`
	}
	out := make([]normalizedField, 0, len(fields))
	for _, field := range fields {
		out = append(out, normalizedField{
			FieldAPIName:          strings.TrimSpace(field.FieldAPIName),
			PartitionFieldAPIName: strings.TrimSpace(field.PartitionFieldAPIName),
		})
	}
	sort.SliceStable(out, func(i, j int) bool {
		if !strings.EqualFold(out[i].PartitionFieldAPIName, out[j].PartitionFieldAPIName) {
			return strings.ToLower(out[i].PartitionFieldAPIName) < strings.ToLower(out[j].PartitionFieldAPIName)
		}
		return strings.ToLower(out[i].FieldAPIName) < strings.ToLower(out[j].FieldAPIName)
	})
	return json.Marshal(out)
}

func canonicalExclusiveSiblingFieldsPackValue(fields []models.ExclusiveSiblingField) (string, error) {
	raw, err := canonicalExclusiveSiblingFields(fields)
	if err != nil {
		return "", err
	}
	return string(raw), nil
}
