package rules

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/olivere/elastic/v7"
)

// Predicate is a rule predicate
type Predicate struct {
	Field    string        `json:"field"`
	Operands []interface{} `json:"operands"`
}

// NewPredicate Create new QueryString predicate
// TODO: Need to think about fields agrument, to search only in limited set of fields. See https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-multi-field
func NewPredicate(field string, operands ...interface{}) Predicate {
	return Predicate{Field: field, Operands: operands}
}

// Create query for Query String
func (predicate Predicate) sourceQueryString() (interface{}, error) {

	if len(predicate.Operands) != 1 {
		return nil, fmt.Errorf("bad operands count, expect %v, got %v", 1, len(predicate.Operands))
	}

	value, ok := predicate.Operands[0].(string)
	if !ok {
		return nil, fmt.Errorf("bad operand type, need string, got %T", predicate.Operands[0])
	}

	if predicate.Field != "" {
		return elastic.NewQueryStringQuery(value).DefaultField(predicate.Field).Source()
	}

	return elastic.NewQueryStringQuery(value).Source()
}

// Source returns the JSON-serializable query request.
func (predicate Predicate) Source() (interface{}, error) {

	return predicate.sourceQueryString()

}

// Parse predicate recursive
func getPredicate(p interface{}) (*Predicate, error) {
	result := new(Predicate)
	var value map[string]interface{}

	value, ok := p.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("bad interface, need map[string]interface{}, got %T", p)
	}

	// Field
	if _, ok := value["field"]; ok {
		if v, ok := value["field"].(string); ok {
			result.Field = v
		} else {
			return nil, fmt.Errorf("bad preticat field: %T, %v", value["field"], value["field"])
		}
	}

	// Operands
	var operands interface{}
	if cur, ok := value["operands"]; ok {
		operands = cur
	} else if cur, ok := value["value"]; ok {
		operands = cur
	} else {
		return nil, errors.New("no operands found")
	}

	// Now, processing operands
	switch v := operands.(type) {
	case string:
		result.Operands = make([]interface{}, 1)
		result.Operands[0] = v
	case []interface{}:
		result.Operands = make([]interface{}, len(v))

		for i, cur := range v {
			switch curOperand := cur.(type) {
			case string:
				result.Operands[i] = curOperand
			case interface{}:
				op, err := getPredicate(curOperand)
				if err != nil {
					return nil, fmt.Errorf("can't parse predicat's operand #%v -> %v (%v)", i, curOperand, err)
				}
				result.Operands[i] = op
			default:
				return nil, fmt.Errorf("can't parse operand #%v with type %T and value %v", i, v, v)

			}
		}
	default:
		return nil, fmt.Errorf("can't parse operands with type %T and value %v", v, v)
	}

	return result, nil
}

func (predicate *Predicate) UnmarshalJSON(b []byte) error {
	var alias interface{}
	err := json.Unmarshal(b, &alias)
	if err != nil {
		return err
	}

	res, err := getPredicate(alias)
	if err != nil {
		return err
	}

	predicate.Field = res.Field
	predicate.Operands = res.Operands

	return nil
}
