package automation

import (
	"fmt"
	"strings"
	"unicode"
)

// evaluateConditionLogic combines per-condition booleans according to the
// Salesforce-style conditionLogic expression on a flow rule.
//
// Accepted shapes:
//   - "" or "and" (case-insensitive)        → AND of all conditions
//   - "or" (case-insensitive)               → OR of all conditions
//   - any expression over 1-indexed condition references combined with
//     AND, OR, NOT, and parens, e.g. "(1 OR 2) AND 3", "NOT 1 AND 2"
//
// On any parse error or out-of-range index reference, the function returns
// false. That choice mirrors the prior naive evaluator's "fail closed"
// behavior on malformed input — a rule with broken logic should not fire a
// validation/customError or recordUpdate by accident.
//
// Salesforce reference: Flow Builder rule conditionLogic strings use
// 1-indexed integer tokens that reference the entries of the rule's
// conditions[] array, joined with AND / OR / NOT and parens. See
// "Configure Flow Logic" in the Flow API docs.
func evaluateConditionLogic(logic string, results []bool) bool {
	trimmed := strings.TrimSpace(logic)
	if trimmed == "" {
		return andAll(results)
	}
	lower := strings.ToLower(trimmed)
	if lower == "and" {
		return andAll(results)
	}
	if lower == "or" {
		return orAll(results)
	}

	expr, err := parseConditionLogic(trimmed)
	if err != nil {
		return false
	}
	// Validate every ref index up-front so OR / NOT short-circuiting cannot
	// silently swallow an out-of-range reference. Without this, expressions
	// like "1 OR 4" against 3 conditions would evaluate true on the first
	// branch and never report the bad ref — fail closed instead.
	if !expr.refsInRange(len(results)) {
		return false
	}
	v, ok := expr.eval(results)
	if !ok {
		return false
	}
	return v
}

// refsInRange returns true iff every "ref" node in the AST has a 1-indexed
// index that falls within [1, numResults]. Walks the entire tree without
// short-circuiting — any out-of-range ref makes the whole expression
// invalid (fail closed).
func (n *conditionLogicNode) refsInRange(numResults int) bool {
	switch n.kind {
	case "ref":
		return n.index >= 1 && n.index <= numResults
	case "not":
		return n.child != nil && n.child.refsInRange(numResults)
	case "and", "or":
		for _, c := range n.children {
			if c == nil || !c.refsInRange(numResults) {
				return false
			}
		}
		return true
	}
	return false
}

func andAll(results []bool) bool {
	for _, r := range results {
		if !r {
			return false
		}
	}
	return true
}

func orAll(results []bool) bool {
	for _, r := range results {
		if r {
			return true
		}
	}
	return false
}

// --- Parser ---------------------------------------------------------------

// conditionLogicNode is a node in the boolean AST produced by the parser.
// Three concrete shapes:
//   - {kind: "ref",  index: N}         → condition results[N-1]
//   - {kind: "not",  child: …}         → !child
//   - {kind: "and"|"or", children: []} → fold AND / OR
type conditionLogicNode struct {
	kind     string // "ref" | "not" | "and" | "or"
	index    int
	child    *conditionLogicNode
	children []*conditionLogicNode
}

func (n *conditionLogicNode) eval(results []bool) (bool, bool) {
	switch n.kind {
	case "ref":
		idx := n.index - 1
		if idx < 0 || idx >= len(results) {
			return false, false
		}
		return results[idx], true
	case "not":
		v, ok := n.child.eval(results)
		if !ok {
			return false, false
		}
		return !v, true
	case "and":
		for _, c := range n.children {
			v, ok := c.eval(results)
			if !ok {
				return false, false
			}
			if !v {
				return false, true
			}
		}
		return true, true
	case "or":
		for _, c := range n.children {
			v, ok := c.eval(results)
			if !ok {
				return false, false
			}
			if v {
				return true, true
			}
		}
		return false, true
	}
	return false, false
}

// Tokens: TOK_NUM, TOK_AND, TOK_OR, TOK_NOT, TOK_LPAREN, TOK_RPAREN, TOK_EOF.
type tokenKind int

const (
	tokEOF tokenKind = iota
	tokNum
	tokAnd
	tokOr
	tokNot
	tokLParen
	tokRParen
)

type token struct {
	kind  tokenKind
	value int // populated for tokNum
}

func tokenizeConditionLogic(s string) ([]token, error) {
	var out []token
	i := 0
	for i < len(s) {
		ch := rune(s[i])
		if unicode.IsSpace(ch) {
			i++
			continue
		}
		if ch == '(' {
			out = append(out, token{kind: tokLParen})
			i++
			continue
		}
		if ch == ')' {
			out = append(out, token{kind: tokRParen})
			i++
			continue
		}
		if unicode.IsDigit(ch) {
			j := i
			for j < len(s) && unicode.IsDigit(rune(s[j])) {
				j++
			}
			n := 0
			for k := i; k < j; k++ {
				n = n*10 + int(s[k]-'0')
			}
			if n == 0 {
				return nil, fmt.Errorf("conditionLogic: index 0 is not allowed (1-indexed)")
			}
			out = append(out, token{kind: tokNum, value: n})
			i = j
			continue
		}
		if unicode.IsLetter(ch) {
			j := i
			for j < len(s) && (unicode.IsLetter(rune(s[j])) || unicode.IsDigit(rune(s[j]))) {
				j++
			}
			word := strings.ToUpper(s[i:j])
			switch word {
			case "AND":
				out = append(out, token{kind: tokAnd})
			case "OR":
				out = append(out, token{kind: tokOr})
			case "NOT":
				out = append(out, token{kind: tokNot})
			default:
				return nil, fmt.Errorf("conditionLogic: unexpected keyword %q", word)
			}
			i = j
			continue
		}
		return nil, fmt.Errorf("conditionLogic: unexpected character %q at position %d", ch, i)
	}
	out = append(out, token{kind: tokEOF})
	return out, nil
}

// Recursive-descent parser. Grammar (lowest → highest precedence):
//
//	expr    = orExpr
//	orExpr  = andExpr ("OR" andExpr)*
//	andExpr = unary ("AND" unary)*
//	unary   = "NOT" unary | primary
//	primary = NUMBER | "(" expr ")"
type conditionLogicParser struct {
	toks []token
	pos  int
}

func parseConditionLogic(s string) (*conditionLogicNode, error) {
	toks, err := tokenizeConditionLogic(s)
	if err != nil {
		return nil, err
	}
	p := &conditionLogicParser{toks: toks}
	node, err := p.parseOr()
	if err != nil {
		return nil, err
	}
	if p.peek().kind != tokEOF {
		return nil, fmt.Errorf("conditionLogic: trailing tokens after expression")
	}
	return node, nil
}

func (p *conditionLogicParser) peek() token {
	return p.toks[p.pos]
}

func (p *conditionLogicParser) consume() token {
	t := p.toks[p.pos]
	p.pos++
	return t
}

func (p *conditionLogicParser) parseOr() (*conditionLogicNode, error) {
	left, err := p.parseAnd()
	if err != nil {
		return nil, err
	}
	if p.peek().kind != tokOr {
		return left, nil
	}
	children := []*conditionLogicNode{left}
	for p.peek().kind == tokOr {
		p.consume()
		right, err := p.parseAnd()
		if err != nil {
			return nil, err
		}
		children = append(children, right)
	}
	return &conditionLogicNode{kind: "or", children: children}, nil
}

func (p *conditionLogicParser) parseAnd() (*conditionLogicNode, error) {
	left, err := p.parseUnary()
	if err != nil {
		return nil, err
	}
	if p.peek().kind != tokAnd {
		return left, nil
	}
	children := []*conditionLogicNode{left}
	for p.peek().kind == tokAnd {
		p.consume()
		right, err := p.parseUnary()
		if err != nil {
			return nil, err
		}
		children = append(children, right)
	}
	return &conditionLogicNode{kind: "and", children: children}, nil
}

func (p *conditionLogicParser) parseUnary() (*conditionLogicNode, error) {
	if p.peek().kind == tokNot {
		p.consume()
		child, err := p.parseUnary()
		if err != nil {
			return nil, err
		}
		return &conditionLogicNode{kind: "not", child: child}, nil
	}
	return p.parsePrimary()
}

func (p *conditionLogicParser) parsePrimary() (*conditionLogicNode, error) {
	t := p.peek()
	switch t.kind {
	case tokNum:
		p.consume()
		return &conditionLogicNode{kind: "ref", index: t.value}, nil
	case tokLParen:
		p.consume()
		node, err := p.parseOr()
		if err != nil {
			return nil, err
		}
		if p.peek().kind != tokRParen {
			return nil, fmt.Errorf("conditionLogic: expected ')'")
		}
		p.consume()
		return node, nil
	case tokEOF:
		return nil, fmt.Errorf("conditionLogic: unexpected end of expression")
	}
	return nil, fmt.Errorf("conditionLogic: unexpected token at position %d", p.pos)
}
