package analytics

import (
	"context"
	"regexp"

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

// crtMetadataLookup is the narrow schema seam shared by the CRT column-path
// resolver, synthesizer, and wiring decorator. It is the single consolidated
// interface; both files previously held copies under different names
// (schemaLookup, metadataLookup) that drifted on context threading.
//
// Production wires this to crtMetadataAdapter below, which delegates to
// MetadataSource. Tests inject hand-built fakes (see
// crt_synthesizer_test.go's fakeMetadata).
type crtMetadataLookup interface {
	GetSchema(ctx context.Context, apiName string) *models.ObjectMetadata
}

// crtChildLister is an optional add-on interface: lookups that can enumerate
// the child-relationship children of a parent object implement it. The
// production adapter implements it via MetadataService.GetChildRelationships;
// tests that exercise the synthesizer child-discovery path implement it
// themselves (e.g. fakeMetadata.ListChildren).
//
// Keeping this as a separate interface rather than folding into
// crtMetadataLookup lets property tests feed a schema-only fake without
// having to wire a fake child index.
type crtChildLister interface {
	ListChildren(ctx context.Context, parentAPIName string) []*models.ObjectMetadata
}

// crtMetadataAdapter is the production adapter that wraps the real
// MetadataSource. It implements both crtMetadataLookup and
// crtChildLister so the synthesizer's collectChildCandidates primary strategy
// works against real metadata — the missing adapter was the blocker that
// silently degraded every stored-CRT report to base-only (BLOCKER-3).
type crtMetadataAdapter struct {
	md MetadataSource
}

func (a crtMetadataAdapter) GetSchema(ctx context.Context, apiName string) *models.ObjectMetadata {
	if a.md == nil {
		return nil
	}
	return a.md.GetSchema(ctx, apiName)
}

func (a crtMetadataAdapter) ListChildren(ctx context.Context, parentAPIName string) []*models.ObjectMetadata {
	if a.md == nil {
		return nil
	}
	return a.md.GetChildRelationships(ctx, parentAPIName)
}

// newCRTMetadataAdapter constructs the production adapter. nil md is
// tolerated — every method becomes a no-op returning nil/empty, which lets
// the resolver cleanly degrade in tests and cold-start conditions.
func newCRTMetadataAdapter(md MetadataSource) crtMetadataLookup {
	return crtMetadataAdapter{md: md}
}

// sqlIdent is the identifier safelist used by every CRT path that ultimately
// feeds a string into SQL backticks. Any string that fails this regex must
// never reach the query builder — backtick quoting is defeated by embedded
// backticks, and the Sections JSON / polymorphic marker paths cannot assume
// the CRT authoring surface is trusted input.
//
// The pattern intentionally excludes `.` — multi-segment paths must be split
// and each segment validated individually.
var sqlIdent = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)

// isSafeSQLIdent reports whether s is a conservative SQL identifier. Empty
// strings fail.
func isSafeSQLIdent(s string) bool {
	return s != "" && sqlIdent.MatchString(s)
}
