package querycodec

import (
	"fmt"
	"strings"
	"time"
)

func ParsePredicateFragment(fragment string) (Predicate, error) {
	fragment = strings.TrimSpace(fragment)
	if fragment == "" {
		return Predicate{}, nil
	}
	parser := NewParser(NewLexer(fragment))
	expr, err := parser.parseExpression(LOWEST)
	if err != nil {
		return Predicate{}, err
	}
	if parser.peekToken().Type != TokenEOF {
		return Predicate{}, newValidationError("SOQL", fmt.Sprintf("unexpected token in predicate: %s", parser.peekToken().Literal))
	}
	if len(parser.semiJoinClauses) > 0 {
		return Predicate{}, newValidationError("SOQL", "semi-join subqueries are not supported in predicate fragments")
	}
	return predicateFromExpr(expr)
}

func predicateFromExpr(expr Expr) (Predicate, error) {
	switch e := expr.(type) {
	case nil:
		return Predicate{}, nil
	case *BinaryExpr:
		op := strings.ToUpper(e.Operator)
		switch op {
		case "AND":
			left, err := predicateFromExpr(e.Left)
			if err != nil {
				return Predicate{}, err
			}
			right, err := predicateFromExpr(e.Right)
			if err != nil {
				return Predicate{}, err
			}
			return And(left, right), nil
		case "OR":
			left, err := predicateFromExpr(e.Left)
			if err != nil {
				return Predicate{}, err
			}
			right, err := predicateFromExpr(e.Right)
			if err != nil {
				return Predicate{}, err
			}
			return Or(left, right), nil
		}

		left, err := operandFromExpr(e.Left)
		if err != nil {
			return Predicate{}, err
		}
		right, err := operandFromExpr(e.Right)
		if err != nil {
			return Predicate{}, err
		}
		return ComparisonPredicate(left, formulaOperator(op), right), nil
	case *UnaryExpr:
		if strings.EqualFold(e.Operator, "NOT") {
			right, err := predicateFromExpr(e.Right)
			if err != nil {
				return Predicate{}, err
			}
			return Not(right), nil
		}
	case *ParenExpr:
		return predicateFromExpr(e.Expr)
	}

	operand, err := operandFromExpr(expr)
	if err != nil {
		return Predicate{}, err
	}
	return ExpressionPredicate(operand), nil
}

func operandFromExpr(expr Expr) (Operand, error) {
	switch e := expr.(type) {
	case nil:
		return Operand{}, nil
	case *RelativeDateLiteral:
		return LiteralOperand(RelativeDateValue(e.Value)), nil
	case *Identifier:
		return FieldOperand(e.Value), nil
	case *StringLiteral:
		return LiteralOperand(e.Value), nil
	case *NumberLiteral:
		return RawOperand(e.Value), nil
	case *DateTimeLiteral:
		if !strings.Contains(e.Value, "T") {
			return LiteralOperand(DateLiteralValue(e.Value)), nil
		}
		parsed, err := time.Parse(time.RFC3339Nano, e.Value)
		if err != nil {
			return Operand{}, newValidationError("SOQL", fmt.Sprintf("invalid datetime literal: %s", e.Value))
		}
		return LiteralOperand(DateTimeLiteralValue(parsed.UTC().Format(time.RFC3339Nano))), nil
	case *BooleanLiteral:
		return LiteralOperand(e.Value), nil
	case *NullLiteral:
		return RawOperand("null"), nil
	case *ListLiteral:
		elements := make([]Operand, 0, len(e.Elements))
		for _, element := range e.Elements {
			operand, err := operandFromExpr(element)
			if err != nil {
				return Operand{}, err
			}
			elements = append(elements, operand)
		}
		return ListOperand(elements...), nil
	case *CallExpr:
		args := make([]Operand, 0, len(e.Args))
		for _, arg := range e.Args {
			operand, err := operandFromExpr(arg)
			if err != nil {
				return Operand{}, err
			}
			args = append(args, operand)
		}
		return FunctionOperand(e.Function, args...), nil
	case *ParenExpr:
		return operandFromExpr(e.Expr)
	case *SemiJoinPlaceholder:
		return ListOperand(LiteralOperand(SemiJoinPlaceholderToken(e.Index))), nil
	default:
		return Operand{}, fmt.Errorf("unsupported SOQL expression %T", expr)
	}
}

func formulaOperator(op string) string {
	switch strings.ToUpper(op) {
	case "=":
		return "=="
	case "<>":
		return "!="
	default:
		return strings.ToLower(op)
	}
}
