package packs

import (
	"fmt"
	"sort"
	"strings"

	domainschema "github.com/nexuscrm/kernel/pkg/schema"
)

type relationshipDeclaration struct {
	ObjectAPIName     string   `json:"objectApiName"`
	RelationshipName  string   `json:"relationshipName"`
	ForeignKey        string   `json:"foreignKey"`
	TargetObjectNames []string `json:"targetObjectNames"`
}

type objectIdentityDeclaration struct {
	ObjectAPIName string   `json:"objectApiName"`
	KeyPrefix     string   `json:"keyPrefix,omitempty"`
	Aliases       []string `json:"aliases,omitempty"`
}

type compatibilityDeclarationSet struct {
	Relationships    []relationshipDeclaration   `json:"relationships,omitempty"`
	ObjectIdentities []objectIdentityDeclaration `json:"objectIdentities,omitempty"`
}

// compatibilityDeclarationsFromObjectSpecPayloads derives validation facts from
// object specs. Runtime lookup state is owned by MetadataService; these
// declarations are kept pack-local so verify tests can catch conflicting facts.
func compatibilityDeclarationsFromObjectSpecPayloads(payloads [][]byte, source string) (compatibilityDeclarationSet, error) {
	decls := compatibilityDeclarationSet{}
	for idx, payload := range payloads {
		specs, err := parseObjectSpecsJSON(payload, fmt.Sprintf("%s-%d", source, idx))
		if err != nil {
			return compatibilityDeclarationSet{}, err
		}
		for _, spec := range specs {
			objectAPIName := strings.TrimSpace(spec.APIName)
			if objectAPIName == "" {
				continue
			}
			appendStructuralRelationshipDeclarations(&decls, objectAPIName)
			decls.ObjectIdentities = append(decls.ObjectIdentities, objectIdentityDeclaration{
				ObjectAPIName: objectAPIName,
				KeyPrefix:     strings.TrimSpace(spec.KeyPrefix),
			})
			for _, field := range spec.Fields {
				if len(field.ReferenceTo) == 0 {
					continue
				}
				relationshipName := strings.TrimSpace(field.RelationshipName)
				if relationshipName == "" && strings.HasSuffix(field.APIName, "Id") {
					relationshipName = strings.TrimSuffix(field.APIName, "Id")
				}
				if relationshipName == "" {
					continue
				}
				decls.Relationships = append(decls.Relationships, relationshipDeclaration{
					ObjectAPIName:     objectAPIName,
					RelationshipName:  relationshipName,
					ForeignKey:        field.APIName,
					TargetObjectNames: append([]string(nil), field.ReferenceTo...),
				})
			}
		}
	}
	return canonicalizeCompatibilityDeclarationSet(decls)
}

func compatibilityDeclarationsFromSystemTables() (compatibilityDeclarationSet, error) {
	decls := compatibilityDeclarationSet{}
	for _, def := range systemTableDefinitions() {
		if strings.TrimSpace(def.TableName) == "" {
			continue
		}
		if keyPrefix := strings.TrimSpace(def.KeyPrefix); keyPrefix != "" {
			decls.ObjectIdentities = append(decls.ObjectIdentities, objectIdentityDeclaration{
				ObjectAPIName: def.TableName,
				KeyPrefix:     keyPrefix,
			})
		}
		appendSystemRelationshipDeclarations(&decls, def)
	}
	return canonicalizeCompatibilityDeclarationSet(decls)
}

func appendSystemRelationshipDeclarations(decls *compatibilityDeclarationSet, def domainschema.TableDefinition) {
	for _, column := range def.Columns {
		if len(column.ReferenceTo) == 0 {
			continue
		}
		relationshipName := strings.TrimSpace(column.RelationshipName)
		if relationshipName == "" && strings.HasSuffix(column.Name, "Id") {
			relationshipName = strings.TrimSuffix(column.Name, "Id")
		}
		if relationshipName == "" {
			continue
		}
		decls.Relationships = append(decls.Relationships, relationshipDeclaration{
			ObjectAPIName:     def.TableName,
			RelationshipName:  relationshipName,
			ForeignKey:        column.Name,
			TargetObjectNames: append([]string(nil), column.ReferenceTo...),
		})
	}
}

func appendStructuralRelationshipDeclarations(decls *compatibilityDeclarationSet, objectAPIName string) {
	for _, rel := range []relationshipDeclaration{
		{
			ObjectAPIName:     objectAPIName,
			RelationshipName:  "Owner",
			ForeignKey:        "OwnerId",
			TargetObjectNames: []string{"User"},
		},
		{
			ObjectAPIName:     objectAPIName,
			RelationshipName:  "CreatedBy",
			ForeignKey:        "CreatedById",
			TargetObjectNames: []string{"User"},
		},
		{
			ObjectAPIName:     objectAPIName,
			RelationshipName:  "LastModifiedBy",
			ForeignKey:        "LastModifiedById",
			TargetObjectNames: []string{"User"},
		},
	} {
		decls.Relationships = append(decls.Relationships, rel)
	}
}

func mergeCompatibilityDeclarationSets(sets ...compatibilityDeclarationSet) (compatibilityDeclarationSet, error) {
	merged := compatibilityDeclarationSet{}
	for _, set := range sets {
		merged.Relationships = append(merged.Relationships, set.Relationships...)
		merged.ObjectIdentities = append(merged.ObjectIdentities, set.ObjectIdentities...)
	}
	return canonicalizeCompatibilityDeclarationSet(merged)
}

func canonicalizeCompatibilityDeclarationSet(input compatibilityDeclarationSet) (compatibilityDeclarationSet, error) {
	relationships := make(map[string]relationshipDeclaration)
	for _, rel := range input.Relationships {
		key := strings.ToLower(strings.TrimSpace(rel.ObjectAPIName)) + "|" + strings.ToLower(strings.TrimSpace(rel.RelationshipName))
		current := relationships[key]
		if current.ObjectAPIName == "" {
			current.ObjectAPIName = strings.TrimSpace(rel.ObjectAPIName)
			current.RelationshipName = strings.TrimSpace(rel.RelationshipName)
			current.ForeignKey = strings.TrimSpace(rel.ForeignKey)
		} else if !strings.EqualFold(current.ForeignKey, rel.ForeignKey) || !equalFoldedSet(current.TargetObjectNames, rel.TargetObjectNames) {
			return compatibilityDeclarationSet{}, fmt.Errorf("conflicting relationship declaration for %s.%s", rel.ObjectAPIName, rel.RelationshipName)
		}
		targets := append(current.TargetObjectNames, rel.TargetObjectNames...)
		current.TargetObjectNames = dedupeFolded(targets)
		relationships[key] = current
	}

	identities := make(map[string]objectIdentityDeclaration)
	objectsByKeyPrefix := make(map[string]string)
	for _, ident := range input.ObjectIdentities {
		key := strings.ToLower(strings.TrimSpace(ident.ObjectAPIName))
		current := identities[key]
		if current.ObjectAPIName == "" {
			current.ObjectAPIName = strings.TrimSpace(ident.ObjectAPIName)
		}
		if current.KeyPrefix == "" {
			current.KeyPrefix = strings.TrimSpace(ident.KeyPrefix)
		} else if strings.TrimSpace(ident.KeyPrefix) != "" && current.KeyPrefix != strings.TrimSpace(ident.KeyPrefix) {
			return compatibilityDeclarationSet{}, fmt.Errorf("conflicting object identity declaration for %s", ident.ObjectAPIName)
		}
		current.Aliases = dedupeFolded(append(current.Aliases, ident.Aliases...))
		identities[key] = current

		keyPrefix := strings.TrimSpace(ident.KeyPrefix)
		if keyPrefix == "" {
			continue
		}
		if currentObjectAPIName, ok := objectsByKeyPrefix[keyPrefix]; ok && !strings.EqualFold(currentObjectAPIName, ident.ObjectAPIName) {
			return compatibilityDeclarationSet{}, fmt.Errorf("conflicting object identity key prefix %s for %s and %s", keyPrefix, currentObjectAPIName, ident.ObjectAPIName)
		}
		objectsByKeyPrefix[keyPrefix] = strings.TrimSpace(ident.ObjectAPIName)
	}

	out := compatibilityDeclarationSet{
		Relationships:    make([]relationshipDeclaration, 0, len(relationships)),
		ObjectIdentities: make([]objectIdentityDeclaration, 0, len(identities)),
	}
	for _, rel := range relationships {
		out.Relationships = append(out.Relationships, rel)
	}
	for _, ident := range identities {
		out.ObjectIdentities = append(out.ObjectIdentities, ident)
	}
	sort.Slice(out.Relationships, func(i, j int) bool {
		if out.Relationships[i].ObjectAPIName == out.Relationships[j].ObjectAPIName {
			return out.Relationships[i].RelationshipName < out.Relationships[j].RelationshipName
		}
		return out.Relationships[i].ObjectAPIName < out.Relationships[j].ObjectAPIName
	})
	sort.Slice(out.ObjectIdentities, func(i, j int) bool {
		return out.ObjectIdentities[i].ObjectAPIName < out.ObjectIdentities[j].ObjectAPIName
	})
	return out, nil
}

func dedupeFolded(values []string) []string {
	seen := make(map[string]string, len(values))
	for _, value := range values {
		trimmed := strings.TrimSpace(value)
		if trimmed == "" {
			continue
		}
		key := strings.ToLower(trimmed)
		if _, ok := seen[key]; !ok {
			seen[key] = trimmed
		}
	}
	out := make([]string, 0, len(seen))
	for _, value := range seen {
		out = append(out, value)
	}
	sort.Slice(out, func(i, j int) bool {
		return strings.ToLower(out[i]) < strings.ToLower(out[j])
	})
	return out
}

func equalFoldedSet(left, right []string) bool {
	left = dedupeFolded(left)
	right = dedupeFolded(right)
	if len(left) != len(right) {
		return false
	}
	for i := range left {
		if !strings.EqualFold(left[i], right[i]) {
			return false
		}
	}
	return true
}
