package dbs

import (
	"fmt"
	"strings"

	"go.mongodb.org/mongo-driver/bson"
)

type Operator int

const (
	LIKE Operator = iota
	EXISTS
	IN
	GTE
	LTE
	LT
	GT
	EQUAL
	NOT
)

var str = [...]string{
	"like",
	"exists",
	"in",
	"gte",
	"lte",
	"lt",
	"gt",
	"equal",
	"not",
}

func (m Operator) String() string {
	return str[m]
}

func (m Operator) ToMongoEOperator(k string, value interface{}) bson.E {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered. Error:\n", r)
		}
	}()
	defaultValue := bson.E{Key: k, Value: bson.M{"$regex": ToValueOperator(StringToOperator(m.String()), value)}}
	switch m {
	case LIKE:
		return bson.E{Key: k, Value: bson.M{"$regex": ToValueOperator(StringToOperator(m.String()), value)}}
	case EXISTS:
		return bson.E{Key: k, Value: bson.M{"$exists": ToValueOperator(StringToOperator(m.String()), value)}}
	case IN:
		return bson.E{Key: k, Value: bson.M{"$in": ToValueOperator(StringToOperator(m.String()), value)}}
	case GTE:
		return bson.E{Key: k, Value: bson.M{"$gte": ToValueOperator(StringToOperator(m.String()), value)}}
	case GT:
		return bson.E{Key: k, Value: bson.M{"$gt": ToValueOperator(StringToOperator(m.String()), value)}}
	case LTE:
		return bson.E{Key: k, Value: bson.M{"$lte": ToValueOperator(StringToOperator(m.String()), value)}}
	case LT:
		return bson.E{Key: k, Value: bson.M{"$lt": ToValueOperator(StringToOperator(m.String()), value)}}
	case EQUAL:
		return bson.E{Key: k, Value: value}
	case NOT:
		v := value.(Filters)
		orList := bson.A{}
		andList := bson.A{}
		f := bson.D{}
		for k, filter := range v.Or {
			for _, ff := range filter {
				orList = append(orList, StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
			}
		}
		for k, filter := range v.And {
			for _, ff := range filter {
				andList = append(andList, StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
			}
		}
		if len(orList) > 0 && len(andList) == 0 {
			f = bson.D{{"$or", orList}}
		} else {
			if len(orList) > 0 {
				andList = append(andList, bson.M{"$or": orList})
			}
			f = bson.D{{"$and", andList}}
		}
		return bson.E{Key: "$not", Value: f}
	default:
		return defaultValue
	}
}

func (m Operator) ToMongoOperator(k string, value interface{}) bson.M {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered. Error:\n", r)
		}
	}()
	defaultValue := bson.M{k: bson.M{"$regex": ToValueOperator(StringToOperator(m.String()), value)}}
	switch m {
	case LIKE:
		return bson.M{k: bson.M{"$regex": ToValueOperator(StringToOperator(m.String()), value)}}
	case EXISTS:
		return bson.M{k: bson.M{"$exists": ToValueOperator(StringToOperator(m.String()), value)}}
	case IN:
		return bson.M{k: bson.M{"$in": ToValueOperator(StringToOperator(m.String()), value)}}
	case GTE:
		return bson.M{k: bson.M{"$gte": ToValueOperator(StringToOperator(m.String()), value)}}
	case GT:
		return bson.M{k: bson.M{"$gt": ToValueOperator(StringToOperator(m.String()), value)}}
	case LTE:
		return bson.M{k: bson.M{"$lte": ToValueOperator(StringToOperator(m.String()), value)}}
	case LT:
		return bson.M{k: bson.M{"$lt": ToValueOperator(StringToOperator(m.String()), value)}}
	case EQUAL:
		return bson.M{k: value}
	case NOT:
		v := value.(Filters)
		orList := bson.A{}
		andList := bson.A{}
		f := bson.D{}
		for k, filter := range v.Or {
			for _, ff := range filter {
				orList = append(orList, StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
			}
		}
		for k, filter := range v.And {
			for _, ff := range filter {
				andList = append(andList, StringToOperator(ff.Operator).ToMongoOperator(k, ff.Value))
			}
		}
		if len(orList) > 0 && len(andList) == 0 {
			f = bson.D{{"$or", orList}}
		} else {
			if len(orList) > 0 {
				andList = append(andList, bson.M{"$or": orList})
			}
			f = bson.D{{"$and", andList}}
		}
		return bson.M{"$not": f}
	default:
		return defaultValue
	}
}

func StringToOperator(s string) Operator {
	for i, v := range str {
		if v == s {
			return Operator(i)
		}
	}
	return LIKE
}

func ToValueOperator(operator Operator, value interface{}) interface{} {
	if strings.TrimSpace(fmt.Sprintf("%v", value)) == "*" {
		value = ""
	}
	if operator == LIKE {
		return "(?i).*" + strings.TrimSpace(fmt.Sprintf("%v", value)) + ".*"
	}
	return value
}

type Filters struct {
	And map[string][]Filter `json:"and"`
	Or  map[string][]Filter `json:"or"`
}

type Filter struct {
	Operator string      `json:"operator,omitempty"`
	Value    interface{} `json:"value,omitempty"`
}

type Input = map[string]interface{}

func InputToBson(i Input, isUpdate bool) bson.D {
	input := bson.D{}
	for k, v := range i {
		if k == "id" {
			input = append(input, bson.E{Key: "_id", Value: v})
		} else {
			input = append(input, bson.E{Key: k, Value: v})
		}
	}
	if isUpdate {
		return bson.D{{Key: "$set", Value: input}}
	}
	return input
}