package caql

import (
	"errors"
	"fmt"
	"strconv"

	"github.com/SecurityBrewery/catalyst/generated/caql/parser"
)

var ErrTooComplex = errors.New("unsupported features for index queries, use advanced search instead")

type bleveBuilder struct {
	*parser.BaseCAQLParserListener
	stack []string
	err   error
}

// push is a helper function for pushing new node to the listener Stack.
func (s *bleveBuilder) push(i string) {
	s.stack = append(s.stack, i)
}

// pop is a helper function for poping a node from the listener Stack.
func (s *bleveBuilder) pop() (n string) {
	// Check that we have nodes in the stack.
	size := len(s.stack)
	if size < 1 {
		panic(ErrStack)
	}

	// Pop the last value from the Stack.
	n, s.stack = s.stack[size-1], s.stack[:size-1]

	return
}

func (s *bleveBuilder) binaryPop() (any, any) {
	right, left := s.pop(), s.pop()

	return left, right
}

// ExitExpression is called when production expression is exited.
func (s *bleveBuilder) ExitExpression(ctx *parser.ExpressionContext) {
	switch {
	case ctx.Value_literal() != nil:
		// pass
	case ctx.Reference() != nil:
		// pass
	case ctx.Operator_unary() != nil:
		s.err = ErrTooComplex
	case ctx.T_PLUS() != nil:
		fallthrough
	case ctx.T_MINUS() != nil:
		fallthrough
	case ctx.T_TIMES() != nil:
		fallthrough
	case ctx.T_DIV() != nil:
		fallthrough
	case ctx.T_MOD() != nil:
		s.err = ErrTooComplex
	case ctx.T_RANGE() != nil:
		s.err = ErrTooComplex
	case ctx.T_LT() != nil && ctx.GetEq_op() == nil:
		left, right := s.binaryPop()
		s.push(fmt.Sprintf("%s:<%s", left, right))
	case ctx.T_GT() != nil && ctx.GetEq_op() == nil:
		left, right := s.binaryPop()
		s.push(fmt.Sprintf("%s:>%s", left, right))
	case ctx.T_LE() != nil && ctx.GetEq_op() == nil:
		left, right := s.binaryPop()
		s.push(fmt.Sprintf("%s:<=%s", left, right))
	case ctx.T_GE() != nil && ctx.GetEq_op() == nil:
		left, right := s.binaryPop()
		s.push(fmt.Sprintf("%s:>=%s", left, right))
	case ctx.T_IN() != nil && ctx.GetEq_op() == nil:
		s.err = ErrTooComplex
	case ctx.T_EQ() != nil && ctx.GetEq_op() == nil:
		left, right := s.binaryPop()
		s.push(fmt.Sprintf("%s:%s", left, right))
	case ctx.T_NE() != nil && ctx.GetEq_op() == nil:
		left, right := s.binaryPop()
		s.push(fmt.Sprintf("-%s:%s", left, right))
	case ctx.T_ALL() != nil && ctx.GetEq_op() != nil:
		fallthrough
	case ctx.T_ANY() != nil && ctx.GetEq_op() != nil:
		fallthrough
	case ctx.T_NONE() != nil && ctx.GetEq_op() != nil:
		s.err = ErrTooComplex
	case ctx.T_ALL() != nil && ctx.T_NOT() != nil && ctx.T_IN() != nil:
		fallthrough
	case ctx.T_ANY() != nil && ctx.T_NOT() != nil && ctx.T_IN() != nil:
		fallthrough
	case ctx.T_NONE() != nil && ctx.T_NOT() != nil && ctx.T_IN() != nil:
		s.err = ErrTooComplex
	case ctx.T_LIKE() != nil:
		s.err = errors.New("index queries are like queries by default")
	case ctx.T_REGEX_MATCH() != nil:
		left, right := s.binaryPop()
		if ctx.T_NOT() != nil {
			s.err = ErrTooComplex
		} else {
			s.push(fmt.Sprintf("%s:/%s/", left, right))
		}
	case ctx.T_REGEX_NON_MATCH() != nil:
		s.err = errors.New("index query cannot contain regex non matches, use advanced search instead")
	case ctx.T_AND() != nil:
		left, right := s.binaryPop()
		s.push(fmt.Sprintf("%s %s", left, right))
	case ctx.T_OR() != nil:
		s.err = errors.New("index query cannot contain OR, use advanced search instead")
	case ctx.T_QUESTION() != nil && len(ctx.AllExpression()) == 3:
		s.err = errors.New("index query cannot contain ternary operations, use advanced search instead")
	case ctx.T_QUESTION() != nil && len(ctx.AllExpression()) == 2:
		s.err = errors.New("index query cannot contain ternary operations, use advanced search instead")
	default:
		panic("unknown expression")
	}
}

// ExitReference is called when production reference is exited.
func (s *bleveBuilder) ExitReference(ctx *parser.ReferenceContext) {
	switch {
	case ctx.DOT() != nil:
		reference := s.pop()

		s.push(fmt.Sprintf("%s.%s", reference, ctx.T_STRING().GetText()))
	case ctx.T_STRING() != nil:
		s.push(ctx.T_STRING().GetText())
	case ctx.Compound_value() != nil:
		s.err = ErrTooComplex
	case ctx.Function_call() != nil:
		s.err = ErrTooComplex
	case ctx.T_OPEN() != nil:
		s.err = ErrTooComplex
	case ctx.T_ARRAY_OPEN() != nil:
		s.err = ErrTooComplex
	default:
		panic(fmt.Sprintf("unexpected value: %s", ctx.GetText()))
	}
}

// ExitValue_literal is called when production value_literal is exited.
func (s *bleveBuilder) ExitValue_literal(ctx *parser.Value_literalContext) {
	if ctx.T_QUOTED_STRING() != nil {
		st, err := unquote(ctx.GetText())
		if err != nil {
			panic(err)
		}
		s.push(strconv.Quote(st))
	} else {
		s.push(ctx.GetText())
	}
}
