package analytics

import (
	"strings"

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

// ChildRelationshipResolution is the verdict of resolveChildRelationshipAuthoritative
// for a single (parent object, requested name) tuple.
//
// The four states implement the F1 truth table from issue #153:
//
//	A — no candidate FK on any child object points at parent
//	    -> Status = ChildRelStatusNoCandidates  (caller returns 400 INVALID_QUERY)
//	B — at least one candidate FK has ChildRelationshipName == requested
//	    -> Status = ChildRelStatusMatched       (caller returns the resolved
//	                                             (childObject, fkField); NULL siblings
//	                                             on the same parent do NOT promote
//	                                             to PackReapplyRequired — the matched
//	                                             candidate is authoritative)
//	C — no candidate matches AND at least one candidate has NULL ChildRelationshipName
//	    -> Status = ChildRelStatusPackReapplyRequired
//	                                            (caller returns 500 PACK_REAPPLY_REQUIRED:
//	                                             substrate not yet populated for FKs
//	                                             pointing at this parent)
//	D — no candidate matches AND every candidate has ChildRelationshipName populated
//	    -> Status = ChildRelStatusNoMatch       (caller returns 400 INVALID_QUERY:
//	                                             user typo; substrate is authoritative)
//
// This is the single authoritative resolver — both
// QueryService.executeSubQueries (SOQL parent-child subqueries) and
// crt_synthesizer.findChildRelationship (CRT report joins) delegate
// here, and sobject_describe.go reads the same authoritative
// FieldMetadata.ChildRelationshipName for /describe.childRelationships
// emit. The historical inferRelationshipName ("childAPIName + s")
// heuristic and the parent-side RelationshipName fallback have been
// deleted; this is the only read path for the fact.
type ChildRelationshipResolution struct {
	Status              ChildRelStatus
	ChildObjectAPIName  string
	FKFieldAPIName      string
}

// ChildRelStatus is the closed enum of F1 truth-table outcomes.
type ChildRelStatus int

const (
	// ChildRelStatusNoCandidates corresponds to F1 case A: no FK on any
	// active object points at the requested parent. Callers should map this
	// to a 400 INVALID_QUERY ("No such relationship 'X' on entity 'Y'").
	ChildRelStatusNoCandidates ChildRelStatus = iota
	// ChildRelStatusMatched corresponds to F1 case B: a candidate FK has
	// ChildRelationshipName equal (case-insensitive) to the requested name.
	// Callers should resolve to the returned (ChildObjectAPIName, FKFieldAPIName).
	ChildRelStatusMatched
	// ChildRelStatusPackReapplyRequired corresponds to F1 case C: no
	// candidate matches the requested name AND at least one candidate has
	// ChildRelationshipName IS NULL. Callers should map this to a
	// 500 PACK_REAPPLY_REQUIRED — the parent has at least one FK whose
	// substrate authority has not been populated, so we cannot prove the
	// requested name is a typo vs. unpopulated.
	ChildRelStatusPackReapplyRequired
	// ChildRelStatusNoMatch corresponds to F1 case D: no candidate matches
	// AND every candidate has ChildRelationshipName populated. Callers
	// should map this to a 400 INVALID_QUERY (user typo; substrate is
	// authoritative).
	ChildRelStatusNoMatch
)

// ResolveChildRelationshipAuthoritative implements the F1 4-state truth table
// over the supplied schemas. It does NOT consult inferRelationshipName or any
// "+s" fallback — that is the entire point of the helper.
//
// Inputs:
//   - allSchemas: every active ObjectMetadata in the org (caller passes
//     metadata.GetSchemas(ctx)). The helper iterates fields on each schema.
//   - parentObject: the API name of the parent the caller is trying to
//     subquery from (case-insensitive against ReferenceTo).
//   - requestedName: the relationship name from the SOQL parent-child clause
//     `(SELECT … FROM <requestedName>) FROM <parentObject>` (case-insensitive
//     against the candidate's ChildRelationshipName).
//
// The helper is purely functional: it does not call into any database, cache,
// or metadata service. Callers are responsible for ensuring allSchemas
// reflects the live snapshot at the time of decision.
func ResolveChildRelationshipAuthoritative(
	allSchemas []*models.ObjectMetadata,
	parentObject string,
	requestedName string,
) ChildRelationshipResolution {
	target := strings.TrimSpace(requestedName)
	parent := strings.TrimSpace(parentObject)

	// childRelName is *string so a nil entry is the substrate-not-populated
	// signal that triggers case C; the full candidate set must be collected
	// before we can distinguish case A from case D.
	type candidate struct {
		childObject  string
		fkField      string
		childRelName *string
	}
	var candidates []candidate
	for _, schema := range allSchemas {
		if schema == nil {
			continue
		}
		for i := range schema.Fields {
			f := &schema.Fields[i]
			if !constants.IsReferenceType(constants.NormalizeFieldType(string(f.Type))) {
				continue
			}
			if !fieldPointsAt(f, parent) {
				continue
			}
			candidates = append(candidates, candidate{
				childObject:  schema.APIName,
				fkField:      f.APIName,
				childRelName: f.ChildRelationshipName,
			})
		}
	}

	if len(candidates) == 0 {
		return ChildRelationshipResolution{Status: ChildRelStatusNoCandidates}
	}

	// F1 declares the matched candidate authoritative even if NULL siblings
	// exist on the same parent — case B short-circuits.
	for _, c := range candidates {
		if c.childRelName == nil {
			continue
		}
		if strings.EqualFold(strings.TrimSpace(*c.childRelName), target) {
			return ChildRelationshipResolution{
				Status:             ChildRelStatusMatched,
				ChildObjectAPIName: c.childObject,
				FKFieldAPIName:     c.fkField,
			}
		}
	}

	for _, c := range candidates {
		if c.childRelName == nil {
			return ChildRelationshipResolution{Status: ChildRelStatusPackReapplyRequired}
		}
	}

	return ChildRelationshipResolution{Status: ChildRelStatusNoMatch}
}
