package promql

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/influxdata/flux"
	"github.com/influxdata/flux/ast"
	"github.com/influxdata/flux/interpreter"
	"github.com/influxdata/flux/semantic"
	"github.com/influxdata/flux/stdlib/universe"
	"github.com/influxdata/influxdb/v2/query/stdlib/influxdata/influxdb"
)

type ArgKind int

const (
	IdentifierKind ArgKind = iota
	DurationKind
	ExprKind
	NumberKind
	StringKind
	SelectorKind
)

type QueryBuilder interface {
	QuerySpec() (*flux.Spec, error)
}

type Arg interface {
	Type() ArgKind
	Value() interface{}
}

type Identifier struct {
	Name string `json:"name,omitempty"`
}

func (id *Identifier) Type() ArgKind {
	return IdentifierKind
}

func (id *Identifier) Value() interface{} {
	return id.Name
}

func NewIdentifierList(first *Identifier, rest interface{}) ([]*Identifier, error) {
	ids := []*Identifier{first}
	for _, l := range toIfaceSlice(rest) {
		if id, ok := l.(*Identifier); ok {
			ids = append(ids, id)
		}
	}
	return ids, nil
}

type StringLiteral struct {
	String string `json:"string,omitempty"`
}

func (s *StringLiteral) Type() ArgKind {
	return StringKind
}

func (s *StringLiteral) Value() interface{} {
	return s.String
}

type Duration struct {
	Dur time.Duration `json:"dur,omitempty"`
}

func (d *Duration) Type() ArgKind {
	return DurationKind
}

func (d *Duration) Value() interface{} {
	return d.Dur
}

type Number struct {
	Val float64 `json:"val,omitempty"`
}

func (n *Number) Type() ArgKind {
	return NumberKind
}

func (n *Number) Value() interface{} {
	return n.Val
}

func NewNumber(val string) (*Number, error) {
	num, err := strconv.ParseFloat(val, 64)
	if err != nil {
		return nil, err
	}
	return &Number{num}, nil
}

// MatchKind is an enum for label matching types.
type MatchKind int

// Possible MatchKinds.
const (
	Equal MatchKind = iota
	NotEqual
	RegexMatch
	RegexNoMatch
)

type LabelMatcher struct {
	Name  string    `json:"name,omitempty"`
	Kind  MatchKind `json:"kind,omitempty"`
	Value Arg       `json:"value,omitempty"`
}

func NewLabelMatcher(ident *Identifier, kind MatchKind, value Arg) (*LabelMatcher, error) {
	return &LabelMatcher{
		Name:  ident.Name,
		Kind:  kind,
		Value: value,
	}, nil
}

func NewLabelMatches(first *LabelMatcher, rest interface{}) ([]*LabelMatcher, error) {
	matches := []*LabelMatcher{first}
	for _, m := range toIfaceSlice(rest) {
		if match, ok := m.(*LabelMatcher); ok {
			matches = append(matches, match)
		}
	}
	return matches, nil
}

type Selector struct {
	Name          string          `json:"name,omitempty"`
	Range         time.Duration   `json:"range,omitempty"`
	Offset        time.Duration   `json:"offset,omitempty"`
	LabelMatchers []*LabelMatcher `json:"label_matchers,omitempty"`
}

func (s *Selector) QuerySpec() (*flux.Spec, error) {
	parent := "from"
	ops := []*flux.Operation{
		{
			ID: "from", // TODO: Change this to a UUID
			Spec: &influxdb.FromOpSpec{
				Bucket: influxdb.NameOrID{Name: "prometheus"},
			},
		},
	}
	edges := []flux.Edge{}

	rng, err := NewRangeOp(s.Range, s.Offset)
	if err != nil {
		return nil, err
	}

	if rng != nil {
		ops = append(ops, rng)
		edge := flux.Edge{
			Parent: flux.OperationID(parent),
			Child:  "range",
		}
		parent = "range"
		edges = append(edges, edge)
	}

	where, err := NewWhereOperation(s.Name, s.LabelMatchers)
	if err != nil {
		return nil, err
	}

	ops = append(ops, where)
	edge := flux.Edge{
		Parent: flux.OperationID(parent),
		Child:  "where",
	}
	parent = "where"
	edges = append(edges, edge)

	return &flux.Spec{
		Operations: ops,
		Edges:      edges,
	}, nil
}

func NewRangeOp(rng, offset time.Duration) (*flux.Operation, error) {
	if rng == 0 && offset == 0 {
		return nil, nil
	}
	return &flux.Operation{
		ID: "range", // TODO: Change this to a UUID
		Spec: &universe.RangeOpSpec{
			Start: flux.Time{
				Relative: -rng - offset,
			},
		},
	}, nil
}

var operatorLookup = map[MatchKind]ast.OperatorKind{
	Equal:        ast.EqualOperator,
	NotEqual:     ast.NotEqualOperator,
	RegexMatch:   ast.EqualOperator,
	RegexNoMatch: ast.NotEqualOperator,
}

func NewWhereOperation(metricName string, labels []*LabelMatcher) (*flux.Operation, error) {
	var node semantic.Expression = &semantic.BinaryExpression{
		Operator: ast.EqualOperator,
		Left: &semantic.MemberExpression{
			Object: &semantic.IdentifierExpression{
				Name: "r",
			},
			Property: "_metric",
		},
		Right: &semantic.StringLiteral{
			Value: metricName,
		},
	}
	for _, label := range labels {
		op, ok := operatorLookup[label.Kind]
		if !ok {
			return nil, fmt.Errorf("unknown label match kind %d", label.Kind)
		}
		ref := &semantic.MemberExpression{
			Object: &semantic.IdentifierExpression{
				Name: "r",
			},
			Property: label.Name,
		}
		var value semantic.Expression
		if label.Value.Type() == StringKind {
			value = &semantic.StringLiteral{
				Value: label.Value.Value().(string),
			}
		} else if label.Value.Type() == NumberKind {
			value = &semantic.FloatLiteral{
				Value: label.Value.Value().(float64),
			}
		}
		node = &semantic.LogicalExpression{
			Operator: ast.AndOperator,
			Left:     node,
			Right: &semantic.BinaryExpression{
				Operator: op,
				Left:     ref,
				Right:    value,
			},
		}
	}

	return &flux.Operation{
		ID: "where", // TODO: Change this to a UUID
		Spec: &universe.FilterOpSpec{
			Fn: interpreter.ResolvedFunction{
				Scope: nil,
				Fn: &semantic.FunctionExpression{
					Parameters: &semantic.FunctionParameters{
						List: []*semantic.FunctionParameter{{Key: &semantic.Identifier{Name: "r"}}},
					},
					Block: &semantic.Block{
						Body: []semantic.Statement{
							&semantic.ReturnStatement{
								Argument: node,
							},
						},
					},
				},
			},
		},
	}, nil
}

func (s *Selector) Type() ArgKind {
	return SelectorKind
}

func (s *Selector) Value() interface{} {
	return s.Name // TODO: Change to AST
}

func NewSelector(metric *Identifier, block, rng, offset interface{}) (*Selector, error) {
	sel := &Selector{
		Name: metric.Name,
	}

	if block != nil {
		sel.LabelMatchers = block.([]*LabelMatcher)
	}

	if rng != nil {
		sel.Range = rng.(time.Duration)
	}

	if offset != nil {
		sel.Offset = offset.(time.Duration)
	}

	return sel, nil
}

type Aggregate struct {
	Without bool          `json:"without,omitempty"`
	By      bool          `json:"by,omitempty"`
	Labels  []*Identifier `json:"labels,omitempty"`
}

func (a *Aggregate) QuerySpec() (*flux.Operation, error) {
	if a.Without {
		return nil, fmt.Errorf("unable to merge using `without`")
	}
	keys := make([]string, len(a.Labels))
	for i := range a.Labels {
		keys[i] = a.Labels[i].Name
	}
	return &flux.Operation{
		ID: "merge",
		Spec: &universe.GroupOpSpec{
			Columns: keys,
			Mode:    "by",
		},
	}, nil
}

type OperatorKind int

const (
	UnknownOpKind OperatorKind = iota
	CountValuesKind
	TopKind
	BottomKind
	QuantileKind
	SumKind
	MinKind
	MaxKind
	AvgKind
	StdevKind
	StdVarKind
	CountKind
)

func ToOperatorKind(op string) OperatorKind {
	op = strings.ToLower(op)
	switch op {
	case "count_values":
		return CountValuesKind
	case "topk":
		return TopKind
	case "bottomk":
		return BottomKind
	case "quantile":
		return QuantileKind
	case "sum":
		return SumKind
	case "min":
		return MinKind
	case "max":
		return MaxKind
	case "avg":
		return AvgKind
	case "stddev":
		return StdevKind
	case "stdvar":
		return StdVarKind
	case "count":
		return CountKind
	default:
		return UnknownOpKind
	}
}

type Operator struct {
	Kind OperatorKind `json:"kind,omitempty"`
	Arg  Arg          `json:"arg,omitempty"`
}

func (o *Operator) QuerySpec() (*flux.Operation, error) {
	switch o.Kind {
	case CountValuesKind, BottomKind, QuantileKind, StdVarKind:
		return nil, fmt.Errorf("unable to run %d yet", o.Kind)
	case CountKind:
		return &flux.Operation{
			ID:   "count",
			Spec: &universe.CountOpSpec{},
		}, nil
	//case TopKind:
	//	return &flux.Operation{
	//		ID:   "top",
	//		Spec: &universe.TopOpSpec{}, // TODO: Top doesn't have arg yet
	//	}, nil
	case SumKind:
		return &flux.Operation{
			ID:   "sum",
			Spec: &universe.SumOpSpec{},
		}, nil
	//case MinKind:
	//	return &flux.Operation{
	//		ID:   "min",
	//		Spec: &universe.MinOpSpec{},
	//	}, nil
	//case MaxKind:
	//	return &flux.Operation{
	//		ID:   "max",
	//		Spec: &universe.MaxOpSpec{},
	//	}, nil
	//case AvgKind:
	//	return &flux.Operation{
	//		ID:   "mean",
	//		Spec: &universe.MeanOpSpec{},
	//	}, nil
	//case StdevKind:
	//	return &flux.Operation{
	//		ID:   "stddev",
	//		Spec: &universe.StddevOpSpec{},
	//	}, nil
	default:
		return nil, fmt.Errorf("unknown Op kind %d", o.Kind)
	}
}

type AggregateExpr struct {
	Op        *Operator  `json:"op,omitempty"`
	Selector  *Selector  `json:"selector,omitempty"`
	Aggregate *Aggregate `json:"aggregate,omitempty"`
}

func (a *AggregateExpr) QuerySpec() (*flux.Spec, error) {
	spec, err := a.Selector.QuerySpec()
	if err != nil {
		return nil, err
	}

	if a.Aggregate != nil {
		agg, err := a.Aggregate.QuerySpec()
		if err != nil {
			return nil, err
		}

		parent := flux.OperationID("from")
		if len(spec.Edges) > 0 {
			tail := spec.Edges[len(spec.Edges)-1]
			parent = tail.Child
		}

		spec.Operations = append(spec.Operations, agg)
		spec.Edges = append(spec.Edges, flux.Edge{
			Parent: parent,
			Child:  agg.ID,
		})
	}

	op, err := a.Op.QuerySpec()
	if err != nil {
		return nil, err
	}

	parent := flux.OperationID("from")
	if len(spec.Edges) > 0 {
		tail := spec.Edges[len(spec.Edges)-1]
		parent = tail.Child
	}
	spec.Operations = append(spec.Operations, op)
	spec.Edges = append(spec.Edges, flux.Edge{
		Parent: parent,
		Child:  op.ID,
	})
	return spec, nil
}

func NewAggregateExpr(op *Operator, selector *Selector, group interface{}) (*AggregateExpr, error) {
	expr := &AggregateExpr{
		Op:       op,
		Selector: selector,
	}
	if group != nil {
		expr.Aggregate = group.(*Aggregate)
	}
	return expr, nil
}

type Comment struct {
	Source string `json:"source,omitempty"`
}

func (c *Comment) QuerySpec() (*flux.Spec, error) {
	return nil, fmt.Errorf("unable to represent comments in the AST")
}

func toIfaceSlice(v interface{}) []interface{} {
	if v == nil {
		return nil
	}
	return v.([]interface{})
}
