// Copyright 2022 Molecula Corp. All rights reserved.

package planner

import (
	"context"
	"strconv"
	"strings"

	"github.com/featurebasedb/featurebase/v3/dax"
	"github.com/featurebasedb/featurebase/v3/pql"
	"github.com/featurebasedb/featurebase/v3/sql3"
	"github.com/featurebasedb/featurebase/v3/sql3/parser"
	"github.com/featurebasedb/featurebase/v3/sql3/planner/types"
)

// generatePQLCallFromExpr returns a *pql.Call tree for a given plan expression
func (p *ExecutionPlanner) generatePQLCallFromExpr(ctx context.Context, expr types.PlanExpression) (_ *pql.Call, err error) {
	if expr == nil {
		return nil, nil
	}

	switch expr := expr.(type) {
	case *binOpPlanExpression:
		return p.generatePQLCallFromBinaryExpr(ctx, expr)

	case *callPlanExpression:
		switch strings.ToUpper(expr.name) {
		case "SETCONTAINS":
			col := expr.args[0].(*qualifiedRefPlanExpression)

			pqlValue, err := planExprToValue(expr.args[1])
			if err != nil {
				return nil, err
			}
			return &pql.Call{
				Name: "Row",
				Args: map[string]interface{}{
					col.columnName: pqlValue,
				},
			}, nil

		case "SETCONTAINSALL":
			col := expr.args[0].(*qualifiedRefPlanExpression)

			set, ok := expr.args[1].(*exprSetLiteralPlanExpression)
			if !ok {
				return nil, sql3.NewErrInternalf("unexpected argument type '%T'", expr.args[1])
			}

			call := &pql.Call{
				Name:     "Intersect",
				Children: []*pql.Call{},
			}

			for _, m := range set.members {
				pqlValue, err := planExprToValue(m)
				if err != nil {
					return nil, err
				}
				rc := &pql.Call{
					Name: "Row",
					Args: map[string]interface{}{
						col.columnName: pqlValue,
					},
				}
				call.Children = append(call.Children, rc)
			}
			return call, nil

		case "SETCONTAINSANY":
			col := expr.args[0].(*qualifiedRefPlanExpression)

			set, ok := expr.args[1].(*exprSetLiteralPlanExpression)
			if !ok {
				return nil, sql3.NewErrInternalf("unexpected argument type '%T'", expr.args[1])
			}

			call := &pql.Call{
				Name:     "Union",
				Children: []*pql.Call{},
			}

			for _, m := range set.members {
				pqlValue, err := planExprToValue(m)
				if err != nil {
					return nil, err
				}
				rc := &pql.Call{
					Name: "Row",
					Args: map[string]interface{}{
						col.columnName: pqlValue,
					},
				}
				call.Children = append(call.Children, rc)
			}
			return call, nil

		case "RANGEQ":
			col := expr.args[0].(*qualifiedRefPlanExpression)

			var fromValue interface{}
			switch fromExpr := expr.args[1].(type) {
			case *stringLiteralPlanExpression:
				// parse timestamp from string and use the int value
				ts := fromExpr.ConvertToTimestamp()
				if ts == nil {
					return nil, sql3.NewErrInvalidTypeCoercion(0, 0, fromExpr.value, parser.NewDataTypeTimestamp().TypeDescription())
				}
				fromValue = ts.Unix()

			case *intLiteralPlanExpression:
				// use the int value
				fromValue = fromExpr.value

			case *nullLiteralPlanExpression:
				fromValue = nil

			default:
				return nil, sql3.NewErrInternalf("unexpected argument type '%T'", expr.args[1])
			}

			var toValue interface{}
			switch toExpr := expr.args[2].(type) {
			case *stringLiteralPlanExpression:
				// parse timestamp from string and use the int value
				ts := toExpr.ConvertToTimestamp()
				if ts == nil {
					return nil, sql3.NewErrInvalidTypeCoercion(0, 0, toExpr.value, parser.NewDataTypeTimestamp().TypeDescription())
				}
				toValue = ts.Unix()

			case *intLiteralPlanExpression:
				// use the int value
				toValue = toExpr.value

			case *nullLiteralPlanExpression:
				toValue = nil

			default:
				return nil, sql3.NewErrInternalf("unexpected argument type '%T'", expr.args[1])
			}

			if fromValue == nil && toValue == nil {
				return nil, sql3.NewErrQRangeFromAndToTimeCannotBeBothNull(0, 0)
			}

			var call *pql.Call
			if fromValue == nil && toValue != nil {
				call = &pql.Call{
					Name: "Rows",
					Args: map[string]interface{}{
						"field": strings.ToLower(col.columnName),
						"to":    toValue,
					},
				}
			} else if fromValue != nil && toValue == nil {
				call = &pql.Call{
					Name: "Rows",
					Args: map[string]interface{}{
						"field": strings.ToLower(col.columnName),
						"from":  fromValue,
					},
				}
			} else {
				call = &pql.Call{
					Name: "Rows",
					Args: map[string]interface{}{
						"field": strings.ToLower(col.columnName),
						"from":  fromValue,
						"to":    toValue,
					},
				}
			}

			return call, nil

		default:
			return nil, sql3.NewErrInternalf("unsupported scalar function '%s'", expr.name)
		}

	case *inOpPlanExpression:
		// lhs will be qualified ref
		lhs, ok := expr.lhs.(*qualifiedRefPlanExpression)
		if !ok {
			return nil, sql3.NewErrInternalf("unexpected lhs %T", expr.lhs)
		}

		// rhs is expression list - need to convert to a big OR

		list, ok := expr.rhs.(*exprListPlanExpression)
		if !ok {
			return nil, sql3.NewErrInternalf("unexpected argument type '%T'", expr.rhs)
		}

		// if it is the _id column, we can use ConstRow with a list
		if strings.EqualFold(lhs.columnName, string(dax.PrimaryKeyFieldName)) {
			values := make([]interface{}, len(list.exprs))
			for i, m := range list.exprs {
				pqlValue, err := planExprToValue(m)
				if err != nil {
					return nil, err
				}
				values[i] = pqlValue
			}
			call := &pql.Call{
				Name: "ConstRow",
				Args: map[string]interface{}{
					"columns": values,
				},
				Type: pql.PrecallGlobal,
			}
			return call, nil
		}
		// otherwise, OR them all
		call := &pql.Call{
			Name:     "Union",
			Children: []*pql.Call{},
		}

		for _, m := range list.exprs {
			pqlValue, err := planExprToValue(m)
			if err != nil {
				return nil, err
			}
			rc := &pql.Call{
				Name: "Row",
				Args: map[string]interface{}{
					lhs.columnName: pqlValue,
				},
			}
			call.Children = append(call.Children, rc)
		}
		return call, nil
	case *betweenOpPlanExpression:
		lhs, ok := expr.lhs.(*qualifiedRefPlanExpression)
		if !ok {
			return nil, sql3.NewErrInternalf("expected expression type: planner.qualifiedRefPlanExpression got:%T", expr.lhs)
		}
		rexp, ok := expr.rhs.(*rangePlanExpression)
		if !ok {
			return nil, sql3.NewErrInternalf("expected expression type: planner.rangePlanExpression got:%T", expr.rhs)
		}
		lower, err := planExprToValue(rexp.lhs)
		if err != nil {
			return nil, err
		}
		upper, err := planExprToValue(rexp.rhs)
		if err != nil {
			return nil, err
		}
		call := &pql.Call{
			Name: "Row",
			Args: map[string]interface{}{
				lhs.columnName: &pql.Condition{
					Op:    pql.BETWEEN,
					Value: []interface{}{lower, upper},
				},
			},
		}
		return call, nil
	default:
		return nil, sql3.NewErrInternalf("unexpected expression type: %T", expr)
	}
}

func (p *ExecutionPlanner) generatePQLCallFromBinaryExpr(ctx context.Context, expr *binOpPlanExpression) (_ *pql.Call, err error) {
	switch op := expr.op; op {
	case parser.AND, parser.OR:
		name := "Intersect"
		if op == parser.OR {
			name = "Union"
		}

		x, err := p.generatePQLCallFromExpr(ctx, expr.lhs)
		if err != nil {
			return nil, err
		}
		y, err := p.generatePQLCallFromExpr(ctx, expr.rhs)
		if err != nil {
			return nil, err
		}

		return &pql.Call{
			Name:     name,
			Children: []*pql.Call{x, y},
		}, nil

	case parser.EQ:
		lhs, ok := expr.lhs.(*qualifiedRefPlanExpression)
		if !ok {
			return nil, sql3.NewErrInternalf("unexpected lhs %T", expr.lhs)
		}

		pqlValue, err := planExprToValue(expr.rhs)
		if err != nil {
			return nil, err
		}

		switch typ := expr.lhs.Type().(type) {
		case *parser.DataTypeInt:
			return &pql.Call{
				Name: "Row",
				Args: map[string]interface{}{
					lhs.columnName: &pql.Condition{
						Op:    pql.EQ,
						Value: pqlValue,
					},
				},
			}, nil

		case *parser.DataTypeID:
			if strings.EqualFold(lhs.columnName, string(dax.PrimaryKeyFieldName)) {
				return &pql.Call{
					Name: "ConstRow",
					Args: map[string]interface{}{
						"columns": []interface{}{pqlValue},
					},
					Type: pql.PrecallGlobal,
				}, nil
			}
			return &pql.Call{
				Name: "Row",
				Args: map[string]interface{}{
					lhs.columnName: pqlValue,
				},
			}, nil

		case *parser.DataTypeString:
			if strings.EqualFold(lhs.columnName, string(dax.PrimaryKeyFieldName)) {
				return &pql.Call{
					Name: "ConstRow",
					Args: map[string]interface{}{
						"columns": []interface{}{pqlValue},
					},
					Type: pql.PrecallGlobal,
				}, nil
			}
			return &pql.Call{
				Name: "Row",
				Args: map[string]interface{}{
					lhs.columnName: pqlValue,
				},
			}, nil

		case *parser.DataTypeTimestamp:
			return &pql.Call{
				Name: "Row",
				Args: map[string]interface{}{
					lhs.columnName: &pql.Condition{
						Op:    pql.EQ,
						Value: pqlValue,
					},
				},
			}, nil

		case *parser.DataTypeBool:
			return &pql.Call{
				Name: "Row",
				Args: map[string]interface{}{
					lhs.columnName: pqlValue,
				},
			}, nil

		case *parser.DataTypeDecimal:
			val, ok := pqlValue.(float64)
			if !ok {
				return nil, sql3.NewErrInternalf("unexpected type '%T", pqlValue)
			}
			d := pql.FromFloat64(val)
			return &pql.Call{
				Name: "Row",
				Args: map[string]interface{}{
					lhs.columnName: &pql.Condition{
						Op:    pql.EQ,
						Value: d,
					},
				},
			}, nil

		default:
			return nil, sql3.NewErrInternalf("unsupported type for binary expression: %v (%T)", typ, typ)
		}

	case parser.NE:
		lhs, ok := expr.lhs.(*qualifiedRefPlanExpression)
		if !ok {
			return nil, sql3.NewErrInternalf("unexpected lhs %T", expr.lhs)
		}

		pqlValue, err := planExprToValue(expr.rhs)
		if err != nil {
			return nil, err
		}

		switch typ := expr.lhs.Type().(type) {
		case *parser.DataTypeInt:
			return &pql.Call{
				Name: "Row",
				Args: map[string]interface{}{
					lhs.columnName: &pql.Condition{
						Op:    pql.NEQ,
						Value: pqlValue,
					},
				},
			}, nil

		case *parser.DataTypeID:
			return nil, sql3.NewErrUnsupported(0, 0, true, "not equal operator on id typed columns")

		case *parser.DataTypeString:
			return nil, sql3.NewErrUnsupported(0, 0, true, "not equal operator on string typed columns")

		case *parser.DataTypeTimestamp:
			return &pql.Call{
				Name: "Row",
				Args: map[string]interface{}{
					lhs.columnName: &pql.Condition{
						Op:    pql.NEQ,
						Value: pqlValue,
					},
				},
			}, nil

		case *parser.DataTypeBool:
			return nil, sql3.NewErrUnsupported(0, 0, true, "not equal operator on bool typed columns")

		case *parser.DataTypeDecimal:
			val, ok := pqlValue.(float64)
			if !ok {
				return nil, sql3.NewErrInternalf("unexpected type '%T", pqlValue)
			}
			d := pql.FromFloat64(val)
			return &pql.Call{
				Name: "Row",
				Args: map[string]interface{}{
					lhs.columnName: &pql.Condition{
						Op:    pql.NEQ,
						Value: d,
					},
				},
			}, nil

		default:
			return nil, sql3.NewErrInternalf("unsupported type for binary expression: %v (%T)", typ, typ)
		}

	case parser.LT, parser.LE, parser.GT, parser.GE:
		lhs, ok := expr.lhs.(*qualifiedRefPlanExpression)
		if !ok {
			return nil, sql3.NewErrInternalf("unexpected lhs %T", expr.lhs)
		}

		pqlValue, err := planExprToValue(expr.rhs)
		if err != nil {
			return nil, err
		}

		switch typ := expr.lhs.Type().(type) {
		case *parser.DataTypeInt:
			pqlOp, err := sqlToPQLOp(op)
			if err != nil {
				return nil, err
			}
			return &pql.Call{
				Name: "Row",
				Args: map[string]interface{}{
					lhs.columnName: &pql.Condition{
						Op:    pqlOp,
						Value: pqlValue,
					},
				},
			}, nil

		case *parser.DataTypeID:
			return nil, sql3.NewErrUnsupported(0, 0, false, "range queries on id typed columns")

		case *parser.DataTypeString:
			return nil, sql3.NewErrUnsupported(0, 0, false, "range queries on string typed columns")

		case *parser.DataTypeTimestamp:
			pqlOp, err := sqlToPQLOp(op)
			if err != nil {
				return nil, err
			}
			return &pql.Call{
				Name: "Row",
				Args: map[string]interface{}{
					lhs.columnName: &pql.Condition{
						Op:    pqlOp,
						Value: pqlValue,
					},
				},
			}, nil

		case *parser.DataTypeDecimal:

			pqlOp, err := sqlToPQLOp(op)
			if err != nil {
				return nil, err
			}
			cond := &pql.Condition{Op: pqlOp}
			switch val := pqlValue.(type) {
			case float64:
				cond.Value = pql.FromFloat64(val)
			case int64:
				cond.Value = pql.FromInt64(val, 0)
			default:
				return nil, sql3.NewErrInternalf("unexpected type '%T", pqlValue)
			}
			return &pql.Call{
				Name: "Row",
				Args: map[string]interface{}{
					lhs.columnName: cond,
				},
			}, nil

		default:
			return nil, sql3.NewErrInternalf("unsupported type for binary expression: %v (%T)", typ, typ)
		}

	case parser.BITAND, parser.BITOR, parser.BITNOT, parser.LSHIFT, parser.RSHIFT:
		return nil, sql3.NewErrInternal("bitwise operators are not supported here")

	case parser.PLUS, parser.MINUS, parser.STAR, parser.SLASH, parser.REM: // +
		return nil, sql3.NewErrInternal("aritmetic operators are not supported here")

	case parser.CONCAT:
		return nil, sql3.NewErrInternal("concatenation operator is not supported here")

	case parser.IN, parser.NOTIN:
		return nil, sql3.NewErrInternal("IN operator is not supported")

	case parser.IS, parser.ISNOT:
		lhs, ok := expr.lhs.(*qualifiedRefPlanExpression)
		if !ok {
			return nil, sql3.NewErrInternalf("unexpected lhs %T", expr.lhs)
		}

		pqlOp := pql.EQ
		if op == parser.ISNOT {
			pqlOp = pql.NEQ
		}
		switch typ := expr.lhs.Type().(type) {
		case *parser.DataTypeID, *parser.DataTypeString, *parser.DataTypeIDSet, *parser.DataTypeStringSet, *parser.DataTypeBool:
			if strings.EqualFold(lhs.columnName, string(dax.PrimaryKeyFieldName)) {
				return nil, sql3.NewErrInvalidColumnInFilterExpression(0, 0, string(dax.PrimaryKeyFieldName), "is/is not null")
			}
			return &pql.Call{
				Name: "Row",
				Args: map[string]interface{}{
					lhs.columnName: &pql.Condition{
						Op:    pqlOp,
						Value: nil,
					},
				},
			}, nil
		case *parser.DataTypeInt, *parser.DataTypeDecimal, *parser.DataTypeTimestamp:
			return &pql.Call{
				Name: "Row",
				Args: map[string]interface{}{
					lhs.columnName: &pql.Condition{
						Op:    pqlOp,
						Value: nil,
					},
				},
			}, nil

		default:
			return nil, sql3.NewErrInvalidTypeInFilterExpression(0, 0, typ.TypeDescription(), "is/is not null")
		}

	case parser.BETWEEN, parser.NOTBETWEEN:
		return nil, sql3.NewErrInternal("BETWEEN operator is not supported")

	default:
		return nil, sql3.NewErrInternalf("unexpected binary expression operator: %s", expr.op)
	}
}

// sqlToPQLOp converts a parser operation token to PQL.
func sqlToPQLOp(op parser.Token) (pql.Token, error) {
	switch op {
	case parser.EQ:
		return pql.EQ, nil
	case parser.NE:
		return pql.NEQ, nil
	case parser.LT:
		return pql.LT, nil
	case parser.LE:
		return pql.LTE, nil
	case parser.GT:
		return pql.GT, nil
	case parser.GE:
		return pql.GTE, nil
	default:
		return pql.ILLEGAL, sql3.NewErrInternalf("cannot convert SQL op %q to PQL", op)
	}
}

// planExprToValue converts a literal parser expression node to a value.
func planExprToValue(expr types.PlanExpression) (interface{}, error) {
	switch expr := expr.(type) {
	case *intLiteralPlanExpression:
		return expr.value, nil
	case *stringLiteralPlanExpression:
		return expr.value, nil
	case *timestampLiteralPlanExpression:
		return expr.value, nil
	case *boolLiteralPlanExpression:
		return expr.value, nil
	case *floatLiteralPlanExpression:
		f, err := strconv.ParseFloat(expr.value, 64)
		if err != nil {
			return nil, err
		}
		return f, nil
	default:
		return nil, sql3.NewErrInternalf("cannot convert SQL expression %T to a literal value", expr)
	}
}
