package automation

import (
	"strconv"
	"strings"

	"github.com/nexuscrm/shared/pkg/models"
	"github.com/rs/zerolog/log"
)

// evaluateFilterLogic evaluates a filter logic expression like "1 AND (2 OR 3 OR 4 OR 5)"
// using 1-based indexed references to filter results. Supports simple "and"/"or" as well.
func evaluateFilterLogic(logic string, results []bool) bool {
	if len(results) == 0 {
		return true
	}

	// Simple cases
	if logic == "" || logic == "and" {
		for _, r := range results {
			if !r {
				return false
			}
		}
		return true
	}
	if logic == "or" {
		for _, r := range results {
			if r {
				return true
			}
		}
		return false
	}

	// Complex expression: parse and evaluate
	tokens := tokenizeFilterLogic(logic)
	pos := 0
	return parseOrExpr(tokens, &pos, results)
}

// tokenizeFilterLogic splits a filter logic string into tokens
func tokenizeFilterLogic(logic string) []string {
	var tokens []string
	i := 0
	for i < len(logic) {
		ch := logic[i]
		if ch == ' ' || ch == '\t' {
			i++
			continue
		}
		if ch == '(' || ch == ')' {
			tokens = append(tokens, string(ch))
			i++
			continue
		}
		// Read a word or number
		start := i
		for i < len(logic) && logic[i] != ' ' && logic[i] != '(' && logic[i] != ')' && logic[i] != '\t' {
			i++
		}
		tokens = append(tokens, strings.ToUpper(logic[start:i]))
	}
	return tokens
}

// parseOrExpr handles OR precedence (lowest)
func parseOrExpr(tokens []string, pos *int, results []bool) bool {
	left := parseAndExpr(tokens, pos, results)
	for *pos < len(tokens) && tokens[*pos] == "OR" {
		*pos++ // consume OR
		right := parseAndExpr(tokens, pos, results)
		left = left || right
	}
	return left
}

// parseAndExpr handles AND precedence (higher than OR)
func parseAndExpr(tokens []string, pos *int, results []bool) bool {
	left := parsePrimary(tokens, pos, results)
	for *pos < len(tokens) && tokens[*pos] == "AND" {
		*pos++ // consume AND
		right := parsePrimary(tokens, pos, results)
		left = left && right
	}
	return left
}

// parsePrimary handles parenthesized expressions and number references
func parsePrimary(tokens []string, pos *int, results []bool) bool {
	if *pos >= len(tokens) {
		return true
	}

	token := tokens[*pos]

	if token == "(" {
		*pos++ // consume (
		result := parseOrExpr(tokens, pos, results)
		if *pos < len(tokens) && tokens[*pos] == ")" {
			*pos++ // consume )
		}
		return result
	}

	// Must be a number reference (1-based)
	*pos++
	idx := 0
	for _, ch := range token {
		if ch >= '0' && ch <= '9' {
			idx = idx*10 + int(ch-'0')
		}
	}
	if idx >= 1 && idx <= len(results) {
		return results[idx-1]
	}
	return true // Unknown reference, allow execution
}

// evaluateConditionLogic applies a Salesforce-style condition logic string to a
// slice of pre-evaluated boolean results (one per FlowCondition, in order).
//
// Dispatch rules:
//   - "" or "and" (case-insensitive, surrounding whitespace tolerated) → all-AND
//   - "or"  (same tolerance)                                           → any-OR
//   - anything else                                                    → custom expression
//
// Custom expression tokens: integer condition references (1-indexed), AND, OR,
// NOT, (, ).  Token matching is case-insensitive; whitespace is ignored.
// Operator precedence (highest to lowest): NOT > AND > OR.
//
// On parse error or out-of-range reference the function falls back to AND (the
// safest/legacy default) and emits a single warn-level log line per call.  It
// never panics.
func evaluateConditionLogic(results []bool, logic string) bool {
	norm := strings.TrimSpace(strings.ToLower(logic))
	switch norm {
	case "", "and":
		for _, r := range results {
			if !r {
				return false
			}
		}
		return true
	case "or":
		for _, r := range results {
			if r {
				return true
			}
		}
		return false
	}

	// Custom expression: tokenise, parse, evaluate.
	val, ok := parseAndEvalConditionExpr(results, logic)
	if !ok {
		// Fallback: AND semantics (conservative / legacy default).
		log.Logger.Warn().
			Str("conditionLogic", logic).
			Msg("flow.evaluateConditionLogic: malformed or out-of-range expression; falling back to AND")
		for _, r := range results {
			if !r {
				return false
			}
		}
		return true
	}
	return val
}

// condToken represents a single lexical token in a condition logic expression.
type condToken struct {
	kind  condTokenKind
	index int // valid when kind == condTokenInt; 1-indexed condition reference
}

type condTokenKind int

const (
	condTokInt  condTokenKind = iota // integer (condition reference)
	condTokAnd                       // AND
	condTokOr                        // OR
	condTokNot                       // NOT
	condTokLPar                      // (
	condTokRPar                      // )
)

// tokeniseCondExpr splits a condition logic expression into tokens.
// Returns (tokens, true) on success, (nil, false) on lex error.
func tokeniseCondExpr(expr string) ([]condToken, bool) {
	var tokens []condToken
	i := 0
	upper := strings.ToUpper(expr)
	for i < len(upper) {
		ch := upper[i]
		if ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' {
			i++
			continue
		}
		if ch == '(' {
			tokens = append(tokens, condToken{kind: condTokLPar})
			i++
			continue
		}
		if ch == ')' {
			tokens = append(tokens, condToken{kind: condTokRPar})
			i++
			continue
		}
		// Keyword or integer: scan until non-alphanumeric
		j := i + 1
		for j < len(upper) && (upper[j] >= 'A' && upper[j] <= 'Z' || upper[j] >= '0' && upper[j] <= '9') {
			j++
		}
		word := upper[i:j]
		i = j
		switch word {
		case "AND":
			tokens = append(tokens, condToken{kind: condTokAnd})
		case "OR":
			tokens = append(tokens, condToken{kind: condTokOr})
		case "NOT":
			tokens = append(tokens, condToken{kind: condTokNot})
		default:
			// Must be a positive integer
			n, err := strconv.Atoi(word)
			if err != nil || n <= 0 {
				return nil, false
			}
			tokens = append(tokens, condToken{kind: condTokInt, index: n})
		}
	}
	return tokens, true
}

// condExprParser is a simple recursive-descent parser for condition expressions.
// Grammar (precedence, highest first):
//
//	expr   = orExpr
//	orExpr = andExpr ( OR andExpr )*
//	andExpr = notExpr ( AND notExpr )*
//	notExpr = NOT notExpr | primary
//	primary = INT | '(' expr ')'
type condExprParser struct {
	tokens  []condToken
	pos     int
	results []bool
	err     bool // set on out-of-range reference or unexpected token
}

func (p *condExprParser) peek() (condToken, bool) {
	if p.pos >= len(p.tokens) {
		return condToken{}, false
	}
	return p.tokens[p.pos], true
}

func (p *condExprParser) consume() condToken {
	t := p.tokens[p.pos]
	p.pos++
	return t
}

func (p *condExprParser) parseExpr() bool {
	return p.parseOr()
}

func (p *condExprParser) parseOr() bool {
	left := p.parseAnd()
	for {
		tok, ok := p.peek()
		if !ok || tok.kind != condTokOr {
			break
		}
		p.consume()
		right := p.parseAnd()
		left = left || right
	}
	return left
}

func (p *condExprParser) parseAnd() bool {
	left := p.parseNot()
	for {
		tok, ok := p.peek()
		if !ok || tok.kind != condTokAnd {
			break
		}
		p.consume()
		right := p.parseNot()
		left = left && right
	}
	return left
}

func (p *condExprParser) parseNot() bool {
	tok, ok := p.peek()
	if ok && tok.kind == condTokNot {
		p.consume()
		return !p.parseNot()
	}
	return p.parsePrimary()
}

func (p *condExprParser) parsePrimary() bool {
	tok, ok := p.peek()
	if !ok {
		p.err = true
		return false
	}
	if tok.kind == condTokLPar {
		p.consume()
		val := p.parseExpr()
		// expect ')'
		closing, ok2 := p.peek()
		if !ok2 || closing.kind != condTokRPar {
			p.err = true
			return false
		}
		p.consume()
		return val
	}
	if tok.kind == condTokInt {
		p.consume()
		idx := tok.index - 1 // convert to 0-indexed
		if idx < 0 || idx >= len(p.results) {
			p.err = true
			return false
		}
		return p.results[idx]
	}
	// Unexpected token
	p.err = true
	return false
}

// parseAndEvalConditionExpr tokenises and evaluates a custom condition
// expression.  Returns (result, true) on success, (false, false) on error.
func parseAndEvalConditionExpr(results []bool, expr string) (bool, bool) {
	tokens, ok := tokeniseCondExpr(expr)
	if !ok {
		return false, false
	}
	if len(tokens) == 0 {
		return false, false
	}
	p := &condExprParser{tokens: tokens, results: results}
	val := p.parseExpr()
	if p.err {
		return false, false
	}
	// Ensure all tokens were consumed (trailing junk is an error)
	if p.pos != len(p.tokens) {
		return false, false
	}
	return val, true
}

// evaluateConditions evaluates each condition in order and applies the logic
// string to the boolean results via evaluateConditionLogic.  See
// evaluateConditionLogic for supported logic values.
func (e *FlowExecutor) evaluateConditions(context map[string]interface{}, conditions []models.FlowCondition, logic string) bool {
	if len(conditions) == 0 {
		return true
	}

	results := make([]bool, len(conditions))
	for i, cond := range conditions {
		results[i] = e.evaluateCondition(context, &cond)
	}

	return evaluateConditionLogic(results, logic)
}
