package reads

import (
	"fmt"
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/influxdata/influxdb/v2/models"
	"github.com/influxdata/influxdb/v2/storage"
	"github.com/influxdata/influxdb/v2/storage/reads/datatypes"
	"github.com/influxdata/influxql"
)

func TestPlannerCondition(t *testing.T) {
	sqry := &floatIterator{
		Points: []storage.SeriesCursorRow{
			{
				Name: []byte("org_bucket"), Tags: models.Tags{
					{Key: models.MeasurementTagKeyBytes, Value: []byte("cpu")},
					{Key: []byte("host"), Value: []byte("host1")},
					{Key: models.FieldKeyTagKeyBytes, Value: []byte("system")},
				},
			},
			{
				Name: []byte("org_bucket"), Tags: models.Tags{
					{Key: models.MeasurementTagKeyBytes, Value: []byte("mem")},
					{Key: []byte("host"), Value: []byte("host1")},
					{Key: models.FieldKeyTagKeyBytes, Value: []byte("user")},
				},
			},
		},
	}

	expr := fmt.Sprintf(`(%[1]s = 'cpu' AND (%[2]s = 'user' OR %[2]s = 'system')) OR (%[1]s = 'mem' AND "_value" = 0)`, datatypes.MeasurementKey, datatypes.FieldKey)
	cond, err := parseExpr(expr)
	if err != nil {
		t.Fatal("ParseExpr", err)
	}

	p := &indexSeriesCursor{
		sqry:         sqry,
		cond:         cond,
		hasValueExpr: true,
	}

	var keys []string
	// In first row, value cond should reduce to "true" and be nil.
	row := p.Next()
	if row.ValueCond != nil {
		t.Errorf("expected nil ValueCond, got %s", row.ValueCond)
	}
	keys = append(keys, string(models.MakeKey(row.Name, row.Tags)))

	// In second row, the value condition applies.
	row = p.Next()
	if want, got := "_value = 0", row.ValueCond.String(); !cmp.Equal(want, got) {
		t.Errorf("unexpected, %s", cmp.Diff(want, got))
	}
	keys = append(keys, string(models.MakeKey(row.Name, row.Tags)))

	expr = `org_bucket,%[2]s=system,%[1]s=cpu,host=host1
org_bucket,%[2]s=user,%[1]s=mem,host=host1`

	expr = fmt.Sprintf(expr, datatypes.MeasurementKey, datatypes.FieldKey)

	exp := strings.Split(expr, "\n")
	if !cmp.Equal(exp, keys) {
		t.Errorf("unexpected, %s", cmp.Diff(exp, keys))
	}
}

// parseExpr parses the given InfluxQL expression and rewrites
// _measurement and _field vars as their storage tag key equivalents.
func parseExpr(expr string) (influxql.Expr, error) {
	e, err := influxql.ParseExpr(expr)
	if err != nil {
		return nil, err
	}

	e = influxql.RewriteExpr(e, func(expr influxql.Expr) influxql.Expr {
		if vr, ok := expr.(*influxql.VarRef); ok {
			switch vr.Val {
			case datatypes.MeasurementKey:
				vr.Val = models.MeasurementTagKey
			case datatypes.FieldKey:
				vr.Val = models.FieldKeyTagKey
			}
		}
		return expr
	})

	return e, nil
}

// floatIterator is a represents an iterator that reads from a slice.
type floatIterator struct {
	Points []storage.SeriesCursorRow
}

// Close is a no-op closer for testing.
func (itr *floatIterator) Close() {
}

func (itr *floatIterator) Next() (*storage.SeriesCursorRow, error) {
	if len(itr.Points) == 0 {
		return nil, nil
	}

	v := &itr.Points[0]
	itr.Points = itr.Points[1:]
	return v, nil
}
