package soql

import (
	"fmt"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/nexuscrm/shared/pkg/queryspec"
	codec "github.com/pingcap-inc/nexuscrm-app-packs/sfcrm/querycodec"
)

type ParseOpts = codec.ParseOpts

const SemiJoinPlaceholderPrefix = codec.SemiJoinPlaceholderPrefix

func SemiJoinPlaceholderToken(index int) string     { return codec.SemiJoinPlaceholderToken(index) }
func SemiJoinPlaceholderBracketed(index int) string { return codec.SemiJoinPlaceholderBracketed(index) }
func EscapeStringValue(value string) string         { return codec.EscapeStringValue(value) }

func ParseSOQL(query string) (*models.QueryRequest, error) {
	parsed, err := codec.ParseSOQL(query)
	if err != nil {
		return nil, err
	}
	if codec.RequiresDateContext(parsed) {
		return nil, fmt.Errorf("relative date literals require an authenticated query calendar context")
	}
	return queryRequestFromCodec(parsed), nil
}

func ParseSOQLWith(query string, opts ParseOpts) (*models.QueryRequest, error) {
	parsed, err := codec.ParseSOQLWith(query, opts)
	if err != nil {
		return nil, err
	}
	if codec.RequiresDateContext(parsed) {
		return nil, fmt.Errorf("relative date literals require an authenticated query calendar context")
	}
	return queryRequestFromCodec(parsed), nil
}

func ParsePredicateFragment(fragment string) (queryspec.Predicate, error) {
	predicate, err := codec.ParsePredicateFragment(fragment)
	if err != nil {
		return queryspec.Predicate{}, err
	}
	if codec.PredicateRequiresDateContext(predicate) {
		return queryspec.Predicate{}, fmt.Errorf("relative date literals require an authenticated query calendar context")
	}
	return predicateFromCodec(predicate), nil
}

func queryRequestFromCodec(value *codec.QueryRequest) *models.QueryRequest {
	if value == nil {
		return nil
	}
	result := &models.QueryRequest{
		ObjectAPIName: value.ObjectAPIName,
		Filter:        predicateFromCodec(value.Filter), FilterExpr: value.FilterExpr,
		SortField: value.SortField, SortDirection: value.SortDirection,
		Limit: value.Limit, Offset: value.Offset, SkipTotalSize: value.SkipTotalSize,
		ScanAll: value.ScanAll, SelectFields: append([]string(nil), value.SelectFields...),
		GroupBy: value.GroupBy, HavingPredicate: predicateFromCodec(value.HavingPredicate), Having: value.Having,
	}
	for _, order := range value.OrderBy {
		result.OrderBy = append(result.OrderBy, models.SortCriterion{Field: order.Field, Direction: order.Direction})
	}
	for _, subquery := range value.SubQueries {
		mapped := models.SubQueryRequest{
			RelationshipName: subquery.RelationshipName,
			SelectFields:     append([]string(nil), subquery.SelectFields...),
			Filter:           predicateFromCodec(subquery.Filter), FilterExpr: subquery.FilterExpr,
			Limit: subquery.Limit,
		}
		for _, order := range subquery.OrderBy {
			mapped.OrderBy = append(mapped.OrderBy, models.SortCriterion{Field: order.Field, Direction: order.Direction})
		}
		result.SubQueries = append(result.SubQueries, mapped)
	}
	for _, clause := range value.SemiJoins {
		result.SemiJoins = append(result.SemiJoins, models.SemiJoinClause{
			OuterField: clause.OuterField, Negate: clause.Negate,
			InnerQuery: queryRequestFromCodec(clause.InnerQuery), Index: clause.Index,
		})
	}
	return result
}

func predicateFromCodec(value codec.Predicate) queryspec.Predicate {
	result := queryspec.Predicate{Field: value.Field, Op: value.Op, Value: scalarFromCodec(value.Value)}
	switch value.Kind {
	case codec.PredicateNone:
		result.Kind = queryspec.PredicateNone
	case codec.PredicateAnd:
		result.Kind = queryspec.PredicateAnd
	case codec.PredicateOr:
		result.Kind = queryspec.PredicateOr
	case codec.PredicateNot:
		result.Kind = queryspec.PredicateNot
	case codec.PredicateExpression:
		result.Kind = queryspec.PredicateExpression
	case codec.PredicateComparison:
		result.Kind = queryspec.PredicateComparison
	case codec.PredicateFieldComparison:
		result.Kind = queryspec.PredicateFieldComparison
	}
	result.Left = operandFromCodec(value.Left)
	result.Right = operandFromCodec(value.Right)
	result.Operand = operandFromCodec(value.Operand)
	for _, child := range value.Children {
		result.Children = append(result.Children, predicateFromCodec(child))
	}
	return result
}

func operandFromCodec(value codec.Operand) queryspec.Operand {
	result := queryspec.Operand{Field: value.Field, Value: scalarFromCodec(value.Value), Raw: value.Raw, Function: value.Function}
	switch value.Kind {
	case codec.OperandNone:
		result.Kind = queryspec.OperandNone
	case codec.OperandField:
		result.Kind = queryspec.OperandField
	case codec.OperandLiteral:
		result.Kind = queryspec.OperandLiteral
	case codec.OperandRaw:
		result.Kind = queryspec.OperandRaw
	case codec.OperandList:
		result.Kind = queryspec.OperandList
	case codec.OperandFunction:
		result.Kind = queryspec.OperandFunction
	}
	for _, item := range value.Args {
		result.Args = append(result.Args, operandFromCodec(item))
	}
	for _, item := range value.Elements {
		result.Elements = append(result.Elements, operandFromCodec(item))
	}
	return result
}

func scalarFromCodec(value any) any {
	switch typed := value.(type) {
	case codec.NumericLiteral:
		return queryspec.NumericLiteral(typed)
	case codec.DateLiteralValue:
		return string(typed)
	case codec.DateTimeLiteralValue:
		return typed.FormulaText()
	default:
		return value
	}
}
