package packs

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

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

func overlaySourceBackedChildCloneRules(source *ResolvedPackSource, components []ManifestComponent, parsedObjectSpecs map[string][]ObjectSpec, objectSpecIndex map[string]sourceBackedObjectSpecTarget) (map[string]map[string]struct{}, error) {
	overlaid := map[string]map[string]struct{}{}
	seen := false
	for _, component := range components {
		if component.Kind != ComponentKindChildCloneRules {
			continue
		}
		if seen {
			return nil, fmt.Errorf("pack %q declares more than one child_clone_rules component; the current object_spec overlay supports exactly one per pack (component %q)", strings.TrimSpace(source.Manifest.Name), strings.TrimSpace(component.BusinessKey))
		}
		seen = true
		payload, err := sourceBackedComponentPayload(source, component)
		if err != nil {
			return nil, fmt.Errorf("%s %s payload: %w", component.Kind, component.BusinessKey, err)
		}
		rulesByObject, err := parseSourceBackedChildCloneRulesPayload(component, payload)
		if err != nil {
			return nil, err
		}
		for objectAPIName, rules := range rulesByObject {
			key := strings.ToLower(strings.TrimSpace(objectAPIName))
			target, ok := objectSpecIndex[key]
			if !ok {
				return nil, fmt.Errorf("child_clone_rules for %s requires declaring object_spec in the same pack", objectAPIName)
			}
			if !hasComponentDependency(component.DependsOn, ComponentKindObjectSpec, target.businessKey) {
				return nil, fmt.Errorf("child_clone_rules for %s requires dependsOn on object_spec %q", objectAPIName, target.businessKey)
			}
			if target.hasRawChildCloneRules {
				return nil, fmt.Errorf("child_clone_rules for %s conflicts with non-empty object_spec.childCloneRules in object_spec %q; author ChildCloneRules through exactly one pack surface", objectAPIName, target.businessKey)
			}
			specs := parsedObjectSpecs[target.businessKey]
			found := false
			for i := range specs {
				if strings.EqualFold(strings.TrimSpace(specs[i].APIName), target.objectAPIName) {
					specs[i].ChildCloneRules = append([]models.ChildCloneRule(nil), rules...)
					found = true
					break
				}
			}
			if !found {
				return nil, fmt.Errorf("child_clone_rules for %s could not find owning object_spec %q", objectAPIName, target.businessKey)
			}
			parsedObjectSpecs[target.businessKey] = specs
			if overlaid[target.businessKey] == nil {
				overlaid[target.businessKey] = map[string]struct{}{}
			}
			overlaid[target.businessKey][strings.ToLower(strings.TrimSpace(target.objectAPIName))] = struct{}{}
		}
	}
	return overlaid, nil
}

func sourceBackedObjectSpecRuntimePayload(rawPayload []byte, specs []ObjectSpec, overlaidObjects map[string]struct{}) ([]byte, error) {
	specByObject := make(map[string]ObjectSpec, len(specs))
	for _, spec := range specs {
		apiName := strings.ToLower(strings.TrimSpace(spec.APIName))
		if apiName != "" {
			specByObject[apiName] = spec
		}
	}
	var rawSpecs []map[string]json.RawMessage
	if err := json.Unmarshal(rawPayload, &rawSpecs); err != nil {
		return nil, err
	}
	for i := range rawSpecs {
		var apiName string
		if err := json.Unmarshal(rawSpecs[i]["apiName"], &apiName); err != nil {
			return nil, fmt.Errorf("parse object apiName at index %d: %w", i, err)
		}
		key := strings.ToLower(strings.TrimSpace(apiName))
		if _, ok := overlaidObjects[key]; !ok {
			continue
		}
		spec, ok := specByObject[key]
		if !ok {
			return nil, fmt.Errorf("overlaid object %s missing parsed object_spec", apiName)
		}
		rules := spec.ChildCloneRules
		if rules == nil {
			rules = []models.ChildCloneRule{}
		}
		encoded, err := json.Marshal(rules)
		if err != nil {
			return nil, fmt.Errorf("marshal childCloneRules for %s: %w", apiName, err)
		}
		rawSpecs[i]["childCloneRules"] = encoded
	}
	return json.Marshal(rawSpecs)
}

func validateSourceBackedObjectSpecAccessPolicySidecarSurface(specs []ObjectSpec, source string) error {
	for _, spec := range specs {
		if spec.AccessPolicy == nil {
			continue
		}
		objectAPIName := strings.TrimSpace(spec.APIName)
		if objectAPIName == "" {
			objectAPIName = "<missing-api-name>"
		}
		if spec.AccessPolicy.RecordMutation != nil {
			return fmt.Errorf("%s object_spec %s cannot author accessPolicy.recordMutation; use an object_record_mutation_policy component so the subfact has one owner, sidecar, and sweeper", source, objectAPIName)
		}
		if spec.AccessPolicy.Lifecycle != nil {
			return fmt.Errorf("%s object_spec %s cannot author accessPolicy.lifecycle; use an object_lifecycle_policy component so the subfact has one owner, sidecar, and sweeper", source, objectAPIName)
		}
	}
	return nil
}

func objectSpecAPINames(specs []ObjectSpec) []string {
	names := make([]string, 0, len(specs))
	for _, spec := range specs {
		if strings.TrimSpace(spec.APIName) != "" {
			names = append(names, strings.TrimSpace(spec.APIName))
		}
	}
	return uniqueSortedNonEmpty(names)
}

func uniqueSortedNonEmpty(values []string) []string {
	seen := map[string]string{}
	for _, value := range values {
		trimmed := strings.TrimSpace(value)
		if trimmed == "" {
			continue
		}
		seen[strings.ToLower(trimmed)] = trimmed
	}
	keys := make([]string, 0, len(seen))
	for key := range seen {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	out := make([]string, 0, len(keys))
	for _, key := range keys {
		out = append(out, seen[key])
	}
	return out
}
