package soql

import (
	"fmt"
	"strconv"
	"strings"

	apperrors "github.com/nexuscrm/backend/pkg/errors"
	"github.com/nexuscrm/shared/pkg/models"
)

// allowedFunctions is the allowlist of recognized SOQL functions.
// Any function call in SELECT or ORDER BY must match this list (case-insensitive).
// Covers Salesforce SOQL aggregate, date, and utility functions.
var allowedFunctions = map[string]bool{
	"COUNT":            true,
	"COUNT_DISTINCT":   true,
	"SUM":             true,
	"AVG":             true,
	"MIN":             true,
	"MAX":             true,
	"TOSTRING":        true,
	"FORMAT":          true,
	"TOLABEL":         true,
	"CONVERTCURRENCY": true,
	"GROUPING":        true,
	"CALENDAR_MONTH":  true,
	"CALENDAR_YEAR":   true,
	"CALENDAR_QUARTER": true,
	"DAY_IN_MONTH":    true,
	"DAY_IN_WEEK":     true,
	"DAY_IN_YEAR":     true,
	"DAY_ONLY":        true,
	"HOUR_IN_DAY":     true,
	"FISCAL_MONTH":    true,
	"FISCAL_QUARTER":  true,
	"FISCAL_YEAR":     true,
	"WEEK_IN_MONTH":   true,
	"WEEK_IN_YEAR":    true,
	"DISTANCE":        true,
	"GEOLOCATION":     true,
}

func isAllowedFunction(name string) bool {
	return allowedFunctions[strings.ToUpper(name)]
}

// parseFunctionCall consumes a function-call token sequence like "COUNT(Id)"
// when the current token is the function name and peek is "(". The context
// parameter is used in error messages (e.g. "ORDER BY", "subquery ORDER BY").
func (p *Parser) parseFunctionCall(context string) (string, error) {
	name := p.curToken().Literal
	if !isAllowedFunction(name) {
		return "", apperrors.NewValidationError("SOQL", fmt.Sprintf("unrecognized function in %s: %s", context, name))
	}
	p.nextToken() // consume (
	result := name + "("
	depth := 1
	for depth > 0 {
		p.nextToken()
		if p.curToken().Type == TokenEOF {
			return "", apperrors.NewValidationError("SOQL", fmt.Sprintf("unexpected EOF in %s function", context))
		}
		result += p.curToken().Literal
		if p.curToken().Literal == "(" {
			depth++
		} else if p.curToken().Literal == ")" {
			depth--
		}
	}
	return result, nil
}

// maxCallDepth is the maximum nesting depth for function calls in WHERE
// clause expressions, preventing stack overflow from malicious input.
const maxCallDepth = 10

// Precedence levels
const (
	_ int = iota
	LOWEST
	OR
	AND
	EQUALS      // =, !=, <>, LIKE, IN, NOT IN
	LESSGREATER // >, <, >=, <=
	PREFIX      // NOT, -
	CALL
)

var precedences = map[string]int{
	"OR":       OR,
	"AND":      AND,
	"=":        EQUALS,
	"!=":       EQUALS,
	"<>":       EQUALS,
	"LIKE":     EQUALS,
	"NOT LIKE": EQUALS,
	"IN":       EQUALS,
	"NOT IN":   EQUALS,
	"<":        LESSGREATER,
	">":        LESSGREATER,
	"<=":       LESSGREATER,
	">=":       LESSGREATER,
}

type (
	prefixParseFn func() (Expr, error)
	infixParseFn  func(Expr, string) (Expr, error)
)

type Parser struct {
	tokens []Token
	pos    int

	callDepth int // current function-call nesting depth in WHERE expressions

	prefixParseFns map[TokenType]prefixParseFn
	infixParseFns  map[string]infixParseFn
}

func NewParser(l *Lexer) *Parser {
	p := &Parser{
		prefixParseFns: make(map[TokenType]prefixParseFn),
		infixParseFns:  make(map[string]infixParseFn),
	}

	// Buffer all tokens
	for {
		tok := l.NextToken()
		p.tokens = append(p.tokens, tok)
		if tok.Type == TokenEOF {
			break
		}
	}

	// Pre-process NOT IN and NOT LIKE
	for i := 0; i < len(p.tokens)-1; i++ {
		lit := strings.ToUpper(p.tokens[i].Literal)
		if lit == "NOT" {
			nextLit := strings.ToUpper(p.tokens[i+1].Literal)
			if nextLit == "IN" || nextLit == "LIKE" {
				p.tokens[i].Literal = lit + " " + nextLit
				p.tokens[i].Type = TokenOperator
				p.tokens = append(p.tokens[:i+1], p.tokens[i+2:]...)
			}
		}
	}

	p.registerPrefix(TokenIdentifier, p.parseIdentifier)
	p.registerPrefix(TokenString, p.parseStringLiteral)
	p.registerPrefix(TokenNumber, p.parseNumberLiteral)
	p.registerPrefix(TokenKeyword, p.parseKeywordPrefix) // TRUE, FALSE, NULL, NOT
	p.registerPrefix(TokenOperator, p.parsePrefixExpression)
	p.registerPrefix(TokenPunctuation, p.parsePunctuationPrefix)

	p.registerInfixOp("=", p.parseInfixExpression)
	p.registerInfixOp("!=", p.parseInfixExpression)
	p.registerInfixOp("<>", p.parseInfixExpression)
	p.registerInfixOp("<", p.parseInfixExpression)
	p.registerInfixOp(">", p.parseInfixExpression)
	p.registerInfixOp("<=", p.parseInfixExpression)
	p.registerInfixOp(">=", p.parseInfixExpression)
	p.registerInfixOp("LIKE", p.parseInfixExpression)
	p.registerInfixOp("NOT LIKE", p.parseInfixExpression)
	p.registerInfixOp("IN", p.parseInfixExpression)
	p.registerInfixOp("NOT IN", p.parseInfixExpression)
	p.registerInfixOp("AND", p.parseInfixExpression)
	p.registerInfixOp("OR", p.parseInfixExpression)

	return p
}

func (p *Parser) registerPrefix(tokenType TokenType, fn prefixParseFn) {
	p.prefixParseFns[tokenType] = fn
}

func (p *Parser) registerInfixOp(op string, fn infixParseFn) {
	p.infixParseFns[strings.ToUpper(op)] = fn
}

func (p *Parser) curToken() Token {
	if p.pos >= len(p.tokens) {
		return p.tokens[len(p.tokens)-1]
	}
	return p.tokens[p.pos]
}

func (p *Parser) peekToken() Token {
	if p.pos+1 >= len(p.tokens) {
		return p.tokens[len(p.tokens)-1]
	}
	return p.tokens[p.pos+1]
}

func (p *Parser) nextToken() {
	if p.pos < len(p.tokens) {
		p.pos++
	}
}

func (p *Parser) curPrecedence() int {
	op := strings.ToUpper(p.curToken().Literal)
	if pr, ok := precedences[op]; ok {
		return pr
	}
	return LOWEST
}

func (p *Parser) peekPrecedence() int {
	op := strings.ToUpper(p.peekToken().Literal)
	if pr, ok := precedences[op]; ok {
		return pr
	}
	return LOWEST
}

func (p *Parser) expectPeek(t TokenType, literalUpper string) bool {
	if p.peekToken().Type == t && (literalUpper == "" || strings.ToUpper(p.peekToken().Literal) == literalUpper) {
		p.nextToken()
		return true
	}
	return false
}

func (p *Parser) Parse() (*models.QueryRequest, error) {
	req := &models.QueryRequest{}

	if strings.ToUpper(p.curToken().Literal) != "SELECT" {
		return nil, apperrors.NewValidationError("SOQL", "expected SELECT")
	}

	fields, subQueries, err := p.parseSelectFields()
	if err != nil {
		return nil, err
	}
	req.SelectFields = fields
	req.SubQueries = subQueries

	if strings.ToUpper(p.peekToken().Literal) != "FROM" {
		return nil, apperrors.NewValidationError("SOQL", "expected FROM")
	}
	p.nextToken() // consume FROM

	p.nextToken()
	if p.curToken().Type != TokenIdentifier && p.curToken().Type != TokenKeyword {
		return nil, apperrors.NewValidationError("SOQL", "expected Object Name after FROM")
	}
	req.ObjectAPIName = p.curToken().Literal
	if strings.ToUpper(req.ObjectAPIName) == "GROUP" {
		req.ObjectAPIName = "Group" // Titlecase edge case
	}

	// Parse optional clauses
	for p.peekToken().Type != TokenEOF {
		nextLit := strings.ToUpper(p.peekToken().Literal)
		switch nextLit {
		case "WHERE":
			p.nextToken() // consume WHERE
			p.nextToken() // step into expr
			expr, err := p.parseExpression(LOWEST)
			if err != nil {
				return nil, err
			}
			if expr != nil {
				req.FilterExpr = expr.String()
			}
		case "GROUP":
			p.nextToken()
			if !p.expectPeek(TokenKeyword, "BY") {
				return nil, apperrors.NewValidationError("SOQL", "expected BY after GROUP")
			}
			p.nextToken()
			req.GroupBy = p.curToken().Literal
		case "HAVING":
			p.nextToken() // HAVING
			p.nextToken()
			expr, err := p.parseExpression(LOWEST)
			if err != nil {
				return nil, err
			}
			if expr != nil {
				req.Having = expr.String()
			}
		case "ORDER":
			p.nextToken()
			if !p.expectPeek(TokenKeyword, "BY") {
				return nil, apperrors.NewValidationError("SOQL", "expected BY after ORDER")
			}
			p.nextToken()
			for {
				if p.curToken().Type == TokenEOF {
					return nil, apperrors.NewValidationError("SOQL", "expected Field Name after ORDER BY")
				}
				field := p.curToken().Literal
				// Handle function calls in ORDER BY (e.g., COUNT(Id) DESC)
				if p.peekToken().Literal == "(" {
					var err error
					field, err = p.parseFunctionCall("ORDER BY")
					if err != nil {
						return nil, err
					}
				}
				dir := "ASC" // default
				if strings.ToUpper(p.peekToken().Literal) == "ASC" || strings.ToUpper(p.peekToken().Literal) == "DESC" {
					p.nextToken()
					dir = strings.ToUpper(p.curToken().Literal)
				}
				if req.SortField == "" {
					req.SortField = field
					req.SortDirection = dir
				}
				req.OrderBy = append(req.OrderBy, models.SortCriterion{Field: field, Direction: dir})

				if p.peekToken().Literal == "," {
					p.nextToken() // consume comma
					p.nextToken() // consume next field
				} else {
					break
				}
			}
		case "LIMIT":
			p.nextToken()
			if !p.expectPeek(TokenNumber, "") {
				return nil, apperrors.NewValidationError("SOQL", fmt.Sprintf("expected Number after LIMIT, got %s", p.curToken().Literal))
			}
			limit, err := strconv.Atoi(p.curToken().Literal)
			if err != nil {
				return nil, apperrors.NewValidationError("SOQL", fmt.Sprintf("invalid LIMIT: %s", p.curToken().Literal))
			}
			req.Limit = limit
		case "OFFSET":
			p.nextToken()
			if !p.expectPeek(TokenNumber, "") {
				return nil, apperrors.NewValidationError("SOQL", fmt.Sprintf("expected Number after OFFSET, got %s", p.curToken().Literal))
			}
			offset, err := strconv.Atoi(p.curToken().Literal)
			if err != nil {
				return nil, apperrors.NewValidationError("SOQL", fmt.Sprintf("invalid OFFSET: %s", p.curToken().Literal))
			}
			req.Offset = offset
		default:
			// Unexpected
			return req, apperrors.NewValidationError("SOQL", fmt.Sprintf("unexpected token in query: %s", nextLit))
		}
	}

	return req, nil
}

func (p *Parser) parseSelectFields() ([]string, []models.SubQueryRequest, error) {
	var fields []string
	var subQueries []models.SubQueryRequest
	for {
		p.nextToken() // Consume field
		if p.curToken().Type == TokenEOF {
			return nil, nil, apperrors.NewValidationError("SOQL", "unexpected EOF in field list")
		}

		lit := strings.ToUpper(p.curToken().Literal)
		if lit == "SELECT" || lit == "FROM" {
			return nil, nil, apperrors.NewValidationError("SOQL", fmt.Sprintf("unexpected keyword in select fields: %s", p.curToken().Literal))
		}

		// Check for subquery: current token is "(" and next is "SELECT"
		if p.curToken().Literal == "(" && strings.ToUpper(p.peekToken().Literal) == "SELECT" {
			sq, err := p.parseSubQuery()
			if err != nil {
				return nil, nil, err
			}
			subQueries = append(subQueries, sq)

			if p.peekToken().Literal == "," {
				p.nextToken() // consume comma
			} else {
				break
			}
			continue
		}

		field := p.curToken().Literal
		// Handle multi-part fields A.B.C
		for p.peekToken().Literal == "." {
			p.nextToken()
			field += "."
			p.nextToken()
			field += p.curToken().Literal
		}

		// Check for Function Call syntax: Name(...)
		if p.peekToken().Literal == "(" {
			if !isAllowedFunction(field) {
				return nil, nil, apperrors.NewValidationError("SOQL", fmt.Sprintf("unrecognized function in SELECT: %s", field))
			}
			p.nextToken() // Consume (
			field += "("
			depth := 1
			for depth > 0 {
				p.nextToken()
				if p.curToken().Type == TokenEOF {
					return nil, nil, apperrors.NewValidationError("SOQL", "unexpected EOF inside function call")
				}
				field += p.curToken().Literal
				if p.curToken().Literal == "(" {
					depth++
				} else if p.curToken().Literal == ")" {
					depth--
				}
			}
		}

		// Check if there's an alias (next token is identifier and not FROM)
		if p.peekToken().Type == TokenIdentifier && strings.ToUpper(p.peekToken().Literal) != "FROM" && strings.ToUpper(p.peekToken().Literal) != "AS" {
			p.nextToken() // consume alias
			field += " " + p.curToken().Literal
		} else if strings.ToUpper(p.peekToken().Literal) == "AS" {
			p.nextToken() // consume AS
			p.nextToken() // consume alias
			field += " " + p.curToken().Literal
		}

		fields = append(fields, field)

		if p.peekToken().Literal == "," {
			p.nextToken() // consume comma
		} else {
			break
		}
	}
	return fields, subQueries, nil
}

// parseSubQuery parses a subquery like (SELECT Id, Name FROM Contacts WHERE Active = true ORDER BY Name LIMIT 10)
// Current position: on "(", peek is "SELECT"
func (p *Parser) parseSubQuery() (models.SubQueryRequest, error) {
	sq := models.SubQueryRequest{}

	// Consume "(" — we're already on it
	// Move to SELECT
	p.nextToken()
	// Now on SELECT, parse inner select fields (flat only, no nested subqueries)
	var innerFields []string
	for {
		p.nextToken() // consume field token
		if p.curToken().Type == TokenEOF {
			return sq, apperrors.NewValidationError("SOQL", "unexpected EOF in subquery field list")
		}
		upperLit := strings.ToUpper(p.curToken().Literal)
		if upperLit == "FROM" {
			break
		}

		field := p.curToken().Literal
		// Handle multi-part fields A.B.C
		for p.peekToken().Literal == "." {
			p.nextToken()
			field += "."
			p.nextToken()
			field += p.curToken().Literal
		}

		// Check for Function Call syntax: Name(...)
		if p.peekToken().Literal == "(" {
			if !isAllowedFunction(field) {
				return sq, apperrors.NewValidationError("SOQL", fmt.Sprintf("unrecognized function in subquery SELECT: %s", field))
			}
			p.nextToken() // Consume (
			field += "("
			depth := 1
			for depth > 0 {
				p.nextToken()
				if p.curToken().Type == TokenEOF {
					return sq, apperrors.NewValidationError("SOQL", "unexpected EOF in subquery SELECT function")
				}
				field += p.curToken().Literal
				if p.curToken().Literal == "(" {
					depth++
				} else if p.curToken().Literal == ")" {
					depth--
				}
			}
		}

		// Check if there's an alias (next token is identifier and not FROM)
		if p.peekToken().Type == TokenIdentifier && strings.ToUpper(p.peekToken().Literal) != "FROM" && strings.ToUpper(p.peekToken().Literal) != "AS" {
			p.nextToken() // consume alias
			field += " " + p.curToken().Literal
		} else if strings.ToUpper(p.peekToken().Literal) == "AS" {
			p.nextToken() // consume AS
			p.nextToken() // consume alias
			field += " " + p.curToken().Literal
		}

		innerFields = append(innerFields, field)

		if p.peekToken().Literal == "," {
			p.nextToken() // consume comma
		} else if strings.ToUpper(p.peekToken().Literal) == "FROM" {
			// next iteration will consume FROM and break
		}
	}
	sq.SelectFields = innerFields

	// Current token is FROM, consume relationship name
	p.nextToken()
	if p.curToken().Type != TokenIdentifier && p.curToken().Type != TokenKeyword {
		return sq, apperrors.NewValidationError("SOQL", fmt.Sprintf("expected relationship name after FROM in subquery, got %s", p.curToken().Literal))
	}
	sq.RelationshipName = p.curToken().Literal

	// Parse optional WHERE, ORDER BY, LIMIT (stop at ")")
	for p.peekToken().Literal != ")" && p.peekToken().Type != TokenEOF {
		nextLit := strings.ToUpper(p.peekToken().Literal)
		switch nextLit {
		case "WHERE":
			p.nextToken() // consume WHERE
			p.nextToken() // step into expr
			expr, err := p.parseExpression(LOWEST)
			if err != nil {
				return sq, err
			}
			if expr != nil {
				sq.FilterExpr = expr.String()
			}
		case "ORDER":
			p.nextToken() // consume ORDER
			if !p.expectPeek(TokenKeyword, "BY") {
				return sq, apperrors.NewValidationError("SOQL", "expected BY after ORDER in subquery")
			}
			p.nextToken() // consume first sort field
			for {
				if p.curToken().Type == TokenEOF {
					return sq, apperrors.NewValidationError("SOQL", "expected field after ORDER BY in subquery")
				}
				field := p.curToken().Literal
				// Handle function calls in ORDER BY (e.g., COUNT(Id) DESC)
				if p.peekToken().Literal == "(" {
					var err error
					field, err = p.parseFunctionCall("subquery ORDER BY")
					if err != nil {
						return sq, err
					}
				}
				dir := "ASC"
				if strings.ToUpper(p.peekToken().Literal) == "ASC" || strings.ToUpper(p.peekToken().Literal) == "DESC" {
					p.nextToken()
					dir = strings.ToUpper(p.curToken().Literal)
				}
				sq.OrderBy = append(sq.OrderBy, models.SortCriterion{Field: field, Direction: dir})

				if p.peekToken().Literal == "," {
					p.nextToken() // consume comma
					p.nextToken() // consume next field
				} else {
					break
				}
			}
		case "LIMIT":
			p.nextToken() // consume LIMIT
			if !p.expectPeek(TokenNumber, "") {
				return sq, apperrors.NewValidationError("SOQL", fmt.Sprintf("expected number after LIMIT in subquery, got %s", p.curToken().Literal))
			}
			limit, err := strconv.Atoi(p.curToken().Literal)
			if err != nil {
				return sq, apperrors.NewValidationError("SOQL", fmt.Sprintf("invalid LIMIT in subquery: %s", p.curToken().Literal))
			}
			sq.Limit = limit
		default:
			return sq, apperrors.NewValidationError("SOQL", fmt.Sprintf("unexpected token in subquery: %s", p.peekToken().Literal))
		}
	}

	// Consume closing ")"
	if p.peekToken().Literal != ")" {
		return sq, apperrors.NewValidationError("SOQL", "expected ')' at end of subquery")
	}
	p.nextToken() // consume )

	return sq, nil
}

// Pratt Parser Methods

func (p *Parser) parseExpression(precedence int) (Expr, error) {
	prefix := p.prefixParseFns[p.curToken().Type]
	if prefix == nil {
		return nil, apperrors.NewValidationError("SOQL", fmt.Sprintf("no prefix parse function for %d (%s)", p.curToken().Type, p.curToken().Literal))
	}
	leftExp, err := prefix()
	if err != nil {
		return nil, err
	}

	for p.peekToken().Type != TokenEOF && precedence < p.peekPrecedence() {
		op := strings.ToUpper(p.peekToken().Literal)
		infix := p.infixParseFns[op]
		if infix == nil {
			return leftExp, nil
		}
		p.nextToken()
		leftExp, err = infix(leftExp, op)
		if err != nil {
			return nil, err
		}
	}

	return leftExp, nil
}

func (p *Parser) parseIdentifier() (Expr, error) {
	val := p.curToken().Literal
	for p.peekToken().Literal == "." {
		p.nextToken()
		val += "."
		p.nextToken()
		val += p.curToken().Literal
	}
	// It could be a function call
	if p.peekToken().Literal == "(" {
		p.nextToken() // consume (
		return p.parseCallExpression(val)
	}
	return &Identifier{Value: val}, nil
}

func (p *Parser) parseCallExpression(function string) (Expr, error) {
	p.callDepth++
	defer func() { p.callDepth-- }()
	if p.callDepth > maxCallDepth {
		return nil, apperrors.NewValidationError("SOQL", "maximum function call nesting depth exceeded")
	}
	expr := &CallExpr{Function: function, Args: []Expr{}}
	if p.peekToken().Literal == ")" {
		p.nextToken() // consume )
		return expr, nil
	}
	p.nextToken() // move to first arg
	args, err := p.parseExpressionList()
	if err != nil {
		return nil, err
	}
	expr.Args = args
	return expr, nil
}

func (p *Parser) parseStringLiteral() (Expr, error) {
	return &StringLiteral{Value: p.curToken().Literal}, nil
}

func (p *Parser) parseNumberLiteral() (Expr, error) {
	return &NumberLiteral{Value: p.curToken().Literal}, nil
}

func (p *Parser) parseKeywordPrefix() (Expr, error) {
	lit := strings.ToUpper(p.curToken().Literal)
	if lit == "TRUE" {
		return &BooleanLiteral{Value: true}, nil
	}
	if lit == "FALSE" {
		return &BooleanLiteral{Value: false}, nil
	}
	if lit == "NULL" {
		return &NullLiteral{}, nil
	}
	if lit == "NOT" {
		return p.parsePrefixExpression()
	}
	return nil, apperrors.NewValidationError("SOQL", fmt.Sprintf("unexpected keyword in expression: %s", lit))
}

func (p *Parser) parsePrefixExpression() (Expr, error) {
	expr := &UnaryExpr{Operator: p.curToken().Literal}
	p.nextToken()
	right, err := p.parseExpression(PREFIX)
	if err != nil {
		return nil, err
	}
	expr.Right = right
	return expr, nil
}

func (p *Parser) parsePunctuationPrefix() (Expr, error) {
	if p.curToken().Literal == "(" {
		p.nextToken()
		expr, err := p.parseExpression(LOWEST)
		if err != nil {
			return nil, err
		}
		if p.peekToken().Literal != ")" {
			return nil, apperrors.NewValidationError("SOQL", "expected ')'")
		}
		p.nextToken() // consume )
		return &ParenExpr{Expr: expr}, nil
	}
	return nil, apperrors.NewValidationError("SOQL", fmt.Sprintf("unexpected punctuation: %s", p.curToken().Literal))
}

func (p *Parser) parseInfixExpression(left Expr, op string) (Expr, error) {
	expr := &BinaryExpr{
		Left:     left,
		Operator: op,
	}
	precedence := precedences[op]

	// Special case for IN and NOT IN -> requires ListLiteral
	if op == "IN" || op == "NOT IN" {
		if p.peekToken().Literal != "(" {
			return nil, apperrors.NewValidationError("SOQL", fmt.Sprintf("expected '(' after %s", op))
		}
		p.nextToken() // consume (
		p.nextToken() // move to first element

		if p.curToken().Literal == ")" {
			expr.Right = &ListLiteral{Elements: []Expr{}}
		} else {
			elements, err := p.parseExpressionList()
			if err != nil {
				return nil, err
			}
			expr.Right = &ListLiteral{Elements: elements}
		}
		return expr, nil
	}

	p.nextToken()
	right, err := p.parseExpression(precedence)
	if err != nil {
		return nil, err
	}
	expr.Right = right
	return expr, nil
}

func (p *Parser) parseExpressionList() ([]Expr, error) {
	var list []Expr

	exp, err := p.parseExpression(LOWEST)
	if err != nil {
		return nil, err
	}
	list = append(list, exp)

	for p.peekToken().Literal == "," {
		p.nextToken() // consume ,
		p.nextToken() // consume next expr
		exp, err := p.parseExpression(LOWEST)
		if err != nil {
			return nil, err
		}
		list = append(list, exp)
	}

	if p.peekToken().Literal != ")" {
		return nil, apperrors.NewValidationError("SOQL", "expected ')' at end of list")
	}
	p.nextToken() // consume )
	return list, nil
}
