package queries

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"reflect"
	"regexp"
	"strings"
	"time"

	"github.com/influxdata/influxdb/influxql"
)

type literalJSON struct {
	Expr string `json:"expr"`
	Val  string `json:"val"`
	Type string `json:"type"`
}

func ParseSelect(q string) (*SelectStatement, error) {
	stmt, err := influxql.NewParser(strings.NewReader(q)).ParseStatement()
	if err != nil {
		return nil, err
	}
	s, ok := stmt.(*influxql.SelectStatement)
	if !ok {
		return nil, fmt.Errorf("Error parsing query: not a SELECT statement")
	}
	return &SelectStatement{s}, nil
}

type BinaryExpr struct {
	*influxql.BinaryExpr
}

func (b *BinaryExpr) MarshalJSON() ([]byte, error) {
	octets, err := MarshalJSON(b.BinaryExpr.LHS)
	if err != nil {
		log.Fatalln(err)
		return nil, err
	}
	lhs := json.RawMessage(octets)

	octets, err = MarshalJSON(b.BinaryExpr.RHS)
	if err != nil {
		log.Fatalln(err)
		return nil, err
	}
	rhs := json.RawMessage(octets)

	return json.Marshal(struct {
		Expr string           `json:"expr"`
		Op   string           `json:"op"`
		LHS  *json.RawMessage `json:"lhs"`
		RHS  *json.RawMessage `json:"rhs"`
	}{"binary", b.Op.String(), &lhs, &rhs})
}

type Call struct {
	*influxql.Call
}

func (c *Call) MarshalJSON() ([]byte, error) {
	args := make([]json.RawMessage, len(c.Args))
	for i, arg := range c.Args {
		b, err := MarshalJSON(arg)
		if err != nil {
			return nil, err
		}
		args[i] = b
	}
	return json.Marshal(struct {
		Expr string            `json:"expr"`
		Name string            `json:"name"`
		Args []json.RawMessage `json:"args,omitempty"`
	}{"call", c.Name, args})
}

type Distinct struct {
	*influxql.Distinct
}

func (d *Distinct) MarshalJSON() ([]byte, error) {
	return []byte(fmt.Sprintf(`{"expr": "distinct", "val": "%s"}`, d.Val)), nil
}

type Fill struct {
	Option influxql.FillOption
	Value  interface{}
}

func (f *Fill) MarshalJSON() ([]byte, error) {
	var fill string
	switch f.Option {
	case influxql.NullFill:
		fill = "null"
	case influxql.NoFill:
		fill = "none"
	case influxql.PreviousFill:
		fill = "previous"
	case influxql.LinearFill:
		fill = "linear"
	case influxql.NumberFill:
		fill = fmt.Sprintf("%v", f.Value)
	}
	return json.Marshal(fill)
}

type ParenExpr struct {
	*influxql.ParenExpr
}

func (p *ParenExpr) MarshalJSON() ([]byte, error) {
	expr, err := MarshalJSON(p.Expr)
	if err != nil {
		log.Fatalln(err)
		return nil, err
	}
	return []byte(fmt.Sprintf(`{"expr": "paren", "val": %s}`, expr)), nil
}

func LiteralJSON(lit string, litType string) ([]byte, error) {
	result := literalJSON{
		Expr: "literal",
		Val:  lit,
		Type: litType,
	}
	return json.Marshal(result)
}

type BooleanLiteral struct {
	*influxql.BooleanLiteral
}

func (b *BooleanLiteral) MarshalJSON() ([]byte, error) {
	return LiteralJSON(b.String(), "boolean")
}

type DurationLiteral struct {
	*influxql.DurationLiteral
}

func (d *DurationLiteral) MarshalJSON() ([]byte, error) {
	return LiteralJSON(d.String(), "duration")
}

type IntegerLiteral struct {
	*influxql.IntegerLiteral
}

func (i *IntegerLiteral) MarshalJSON() ([]byte, error) {
	return LiteralJSON(i.String(), "integer")
}

type NumberLiteral struct {
	*influxql.NumberLiteral
}

func (n *NumberLiteral) MarshalJSON() ([]byte, error) {
	return LiteralJSON(n.String(), "number")
}

type RegexLiteral struct {
	*influxql.RegexLiteral
}

func (r *RegexLiteral) MarshalJSON() ([]byte, error) {
	return LiteralJSON(r.String(), "regex")
}

// TODO: I don't think list is right
type ListLiteral struct {
	*influxql.ListLiteral
}

func (l *ListLiteral) MarshalJSON() ([]byte, error) {
	vals := make([]string, len(l.Vals))
	for i, v := range l.Vals {
		vals[i] = fmt.Sprintf(`"%s"`, v)
	}
	list := "[" + strings.Join(vals, ",") + "]"
	return []byte(list), nil
}

type StringLiteral struct {
	*influxql.StringLiteral
}

func (s *StringLiteral) MarshalJSON() ([]byte, error) {
	return LiteralJSON(s.Val, "string")
}

type TimeLiteral struct {
	*influxql.TimeLiteral
}

func (t *TimeLiteral) MarshalJSON() ([]byte, error) {
	return LiteralJSON(t.Val.UTC().Format(time.RFC3339Nano), "time")
}

type VarRef struct {
	*influxql.VarRef
}

func (v *VarRef) MarshalJSON() ([]byte, error) {
	if v.Type != influxql.Unknown {
		return []byte(fmt.Sprintf(`{"expr": "reference", "val": "%s", "type": "%s"}`, v.Val, v.Type.String())), nil
	} else {
		return []byte(fmt.Sprintf(`{"expr": "reference",  "val": "%s"}`, v.Val)), nil
	}
}

type Wildcard struct {
	*influxql.Wildcard
}

func (w *Wildcard) MarshalJSON() ([]byte, error) {
	return []byte(fmt.Sprintf(`{"expr": "wildcard", "val": "%s"}`, w.String())), nil
}

func MarshalJSON(v interface{}) ([]byte, error) {
	switch v.(type) {
	case *influxql.BinaryExpr:
		return json.Marshal(&BinaryExpr{v.(*influxql.BinaryExpr)})
	case *influxql.BooleanLiteral:
		return json.Marshal(&BooleanLiteral{v.(*influxql.BooleanLiteral)})
	case *influxql.Call:
		return json.Marshal(&Call{v.(*influxql.Call)})
	case *influxql.Distinct:
		return json.Marshal(&Distinct{v.(*influxql.Distinct)})
	case *influxql.DurationLiteral:
		return json.Marshal(&DurationLiteral{v.(*influxql.DurationLiteral)})
	case *influxql.IntegerLiteral:
		return json.Marshal(&IntegerLiteral{v.(*influxql.IntegerLiteral)})
	case *influxql.NumberLiteral:
		return json.Marshal(&NumberLiteral{v.(*influxql.NumberLiteral)})
	case *influxql.ParenExpr:
		return json.Marshal(&ParenExpr{v.(*influxql.ParenExpr)})
	case *influxql.RegexLiteral:
		return json.Marshal(&RegexLiteral{v.(*influxql.RegexLiteral)})
	case *influxql.ListLiteral:
		return json.Marshal(&ListLiteral{v.(*influxql.ListLiteral)})
	case *influxql.StringLiteral:
		return json.Marshal(&StringLiteral{v.(*influxql.StringLiteral)})
	case *influxql.TimeLiteral:
		return json.Marshal(&TimeLiteral{v.(*influxql.TimeLiteral)})
	case *influxql.VarRef:
		return json.Marshal(&VarRef{v.(*influxql.VarRef)})
	case *influxql.Wildcard:
		return json.Marshal(&Wildcard{v.(*influxql.Wildcard)})
	default:
		t := reflect.TypeOf(v)
		return nil, fmt.Errorf("Error marshaling query: unknown type %s", t)
	}
}

type Measurement struct {
	Database        string         `json:"database"`
	RetentionPolicy string         `json:"retentionPolicy"`
	Name            string         `json:"name,omitempty"`
	Regex           *regexp.Regexp `json:"regex,omitempty"`
	Type            string         `json:"type"`
}

type Source struct {
	influxql.Source
}

func (s *Source) MarshalJSON() ([]byte, error) {
	switch src := s.Source.(type) {
	case *influxql.Measurement:
		m := Measurement{
			Database:        src.Database,
			RetentionPolicy: src.RetentionPolicy,
			Name:            src.Name,
			Type:            "measurement",
		}
		if src.Regex != nil {
			m.Regex = src.Regex.Val
		}
		return json.Marshal(m)
	default:
		return nil, fmt.Errorf("Error marshaling source.  Subqueries not supported yet")
	}
}

type Sources struct {
	influxql.Sources
}

// TODO: Handle subqueries
func (s *Sources) MarshalJSON() ([]byte, error) {
	srcs := make([]Source, len(s.Sources))
	for i, src := range s.Sources {
		srcs[i] = Source{src}
	}
	return json.Marshal(srcs)
}

type Field struct {
	*influxql.Field
}

func (f *Field) MarshalJSON() ([]byte, error) {
	b, err := MarshalJSON(f.Expr)
	if err != nil {
		return nil, err
	}
	column := json.RawMessage(b)
	return json.Marshal(struct {
		Alias  string           `json:"alias,omitempty"`
		Column *json.RawMessage `json:"column"`
	}{f.Alias, &column})
}

type Fields struct {
	influxql.Fields
}

func (f *Fields) MarshalJSON() ([]byte, error) {
	fields := make([]Field, len(f.Fields))
	for i, field := range f.Fields {
		fields[i] = Field{field}
	}

	return json.Marshal(fields)
}

type Condition struct {
	influxql.Expr
}

func (c *Condition) MarshalJSON() ([]byte, error) {
	return MarshalJSON(c.Expr)
}

type SortField struct {
	*influxql.SortField
}

func (s *SortField) MarshalJSON() ([]byte, error) {
	var order string
	if s.Ascending {
		order = "ascending"
	} else {
		order = "descending"
	}

	return json.Marshal(struct {
		Name  string `json:"name,omitempty"`
		Order string `json:"order,omitempty"`
	}{s.Name, order})
}

type SortFields struct {
	influxql.SortFields
}

func (f *SortFields) MarshalJSON() ([]byte, error) {
	fields := make([]SortField, len(f.SortFields))
	for i, field := range f.SortFields {
		fields[i] = SortField{field}
	}

	return json.Marshal(fields)
}

type Limits struct {
	Limit   int `json:"limit,omitempty"`
	Offset  int `json:"offset,omitempty"`
	SLimit  int `json:"slimit,omitempty"`
	SOffset int `json:"soffset,omitempty"`
}

type SelectStatement struct {
	*influxql.SelectStatement
}

func (s *SelectStatement) MarshalJSON() ([]byte, error) {
	stmt := map[string]interface{}{
		"fields":  &Fields{s.Fields},
		"sources": &Sources{s.Sources},
	}
	if len(s.Dimensions) > 0 {
		stmt["groupBy"] = &Dimensions{s.Dimensions, s.Fill, s.FillValue}
	}
	if s.Condition != nil {
		stmt["condition"] = &Condition{s.Condition}
	}
	if s.Limit != 0 || s.Offset != 0 || s.SLimit != 0 || s.SOffset != 0 {
		stmt["limits"] = &Limits{s.Limit, s.Offset, s.SLimit, s.SOffset}
	}
	if len(s.SortFields) > 0 {
		stmt["orderbys"] = &SortFields{s.SortFields}
	}
	return json.Marshal(stmt)
}

type Dimension struct {
	*influxql.Dimension
}

func (d *Dimension) MarshalJSON() ([]byte, error) {
	switch v := d.Expr.(type) {
	case *influxql.Call:
		if v.Name != "time" {
			return nil, errors.New("time dimension offset function must be now()")
		}
		// Make sure there is exactly one argument.
		if got := len(v.Args); got < 1 || got > 2 {
			return nil, errors.New("time dimension expected 1 or 2 arguments")
		}
		// Ensure the argument is a duration.
		lit, ok := v.Args[0].(*influxql.DurationLiteral)
		if !ok {
			return nil, errors.New("time dimension must have duration argument")
		}
		var offset string
		if len(v.Args) == 2 {
			switch o := v.Args[1].(type) {
			case *influxql.DurationLiteral:
				offset = o.String()
			case *influxql.Call:
				if o.Name != "now" {
					return nil, errors.New("time dimension offset function must be now()")
				} else if len(o.Args) != 0 {
					return nil, errors.New("time dimension offset now() function requires no arguments")
				}
				offset = "now()"
			default:
				return nil, errors.New("time dimension offset must be duration or now()")
			}
		}
		return json.Marshal(struct {
			Interval string `json:"interval"`
			Offset   string `json:"offset,omitempty"`
		}{lit.String(), offset})
	case *influxql.VarRef:
		return json.Marshal(v.Val)
	case *influxql.Wildcard:
		return json.Marshal(v.String())
	case *influxql.RegexLiteral:
		return json.Marshal(v.String())
	}
	return MarshalJSON(d.Expr)
}

type Dimensions struct {
	influxql.Dimensions
	FillOption influxql.FillOption
	FillValue  interface{}
}

func (d *Dimensions) MarshalJSON() ([]byte, error) {
	groupBys := struct {
		Time *json.RawMessage   `json:"time,omitempty"`
		Tags []*json.RawMessage `json:"tags,omitempty"`
		Fill *json.RawMessage   `json:"fill,omitempty"`
	}{}

	for _, dim := range d.Dimensions {
		switch dim.Expr.(type) {
		case *influxql.Call:
			octets, err := json.Marshal(&Dimension{dim})

			if err != nil {
				return nil, err
			}
			time := json.RawMessage(octets)
			groupBys.Time = &time
		default:
			octets, err := json.Marshal(&Dimension{dim})
			if err != nil {
				return nil, err
			}
			tag := json.RawMessage(octets)
			groupBys.Tags = append(groupBys.Tags, &tag)
		}
	}
	if d.FillOption != influxql.NullFill {
		octets, err := json.Marshal(&Fill{d.FillOption, d.FillValue})
		if err != nil {
			return nil, err
		}
		fill := json.RawMessage(octets)
		groupBys.Fill = &fill
	}
	return json.Marshal(groupBys)
}
