package analytics

import (
	"fmt"
	"strings"
)

const AggregateAuthoringRequiresRollupMessage = "cross-child aggregate formulas are not supported for authoring; declare a rollupsummary field with RollupConfig instead"

// FindAggregateAuthoringExprs scans expr for cross-child aggregate authoring
// syntax such as SUM(Child.Field), COUNT(Child.*), or SUM(Child.Field, FK).
// It intentionally works on tokens instead of the full AST so Phase 1 can
// reject aggregate authoring even for legacy aggregate spellings that the
// parser later rejects for more specific runtime reasons.
func FindAggregateAuthoringExprs(expr string) ([]AggregateExpr, error) {
	ast, err := Parse(expr)
	if err == nil {
		return extractAggregates(ast), nil
	}

	parseErr := err
	tokens, err := Tokenize(expr)
	if err != nil {
		return nil, parseErr
	}

	var aggregates []AggregateExpr
	for i := 0; i < len(tokens)-1; i++ {
		if tokens[i].Type != TokIdentifier || tokens[i+1].Type != TokLParen {
			continue
		}
		funcName := strings.ToUpper(tokens[i].Value)
		switch funcName {
		case "SUM", "COUNT", "MIN", "MAX", "AVG":
		default:
			continue
		}

		aggregate, ok := scanAggregateAuthoringCall(funcName, tokens[i+2:])
		if ok {
			aggregates = append(aggregates, aggregate)
		}
	}
	if len(aggregates) > 0 {
		return aggregates, nil
	}
	return nil, parseErr
}

func scanAggregateAuthoringCall(funcName string, tokens []Token) (AggregateExpr, bool) {
	if len(tokens) < 4 {
		return AggregateExpr{}, false
	}
	if tokens[0].Type != TokIdentifier || tokens[1].Type != TokDot {
		return AggregateExpr{}, false
	}

	childObject := tokens[0].Value
	fieldTok := tokens[2]
	if fieldTok.Type != TokIdentifier && fieldTok.Type != TokStar {
		return AggregateExpr{}, false
	}
	childField := fieldTok.Value

	switch tokens[3].Type {
	case TokRParen:
		return AggregateExpr{Func: funcName, ChildObject: childObject, ChildField: childField}, true
	case TokComma:
		if len(tokens) < 6 || tokens[4].Type != TokIdentifier || tokens[5].Type != TokRParen {
			return AggregateExpr{}, false
		}
		return AggregateExpr{
			Func:        funcName,
			ChildObject: childObject,
			ChildField:  childField,
			ForeignKey:  tokens[4].Value,
		}, true
	default:
		return AggregateExpr{}, false
	}
}

// ValidateNoAggregateAuthoring rejects cross-child aggregate authoring while
// leaving scalar functions such as MIN(a, b) or MAX(a, b) untouched.
func ValidateNoAggregateAuthoring(expr string) error {
	aggregates, err := FindAggregateAuthoringExprs(expr)
	if err != nil {
		return err
	}
	if len(aggregates) == 0 {
		return nil
	}
	aggregate := aggregates[0]
	if aggregate.ForeignKey != "" {
		return fmt.Errorf("%s: %s(%s.%s, %s)", AggregateAuthoringRequiresRollupMessage, aggregate.Func, aggregate.ChildObject, aggregate.ChildField, aggregate.ForeignKey)
	}
	return fmt.Errorf("%s: %s(%s.%s)", AggregateAuthoringRequiresRollupMessage, aggregate.Func, aggregate.ChildObject, aggregate.ChildField)
}
