package operator

import (
	"fmt"
	"regexp"
	"strings"

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

type Regex struct {
	Key   string
	Value string
}

func (re Regex) MarshalBSON() ([]byte, error) {
	return filterMarshal("$regex", re.Key, re.Value)
}

type CaseInsensitives struct {
	Key    string
	Values []string
}

func (cis CaseInsensitives) MarshalBSON() ([]byte, error) {
	values := cis.Values
	if len(cis.Values) == 0 {
		values = []string{".*"}
	} else {
		for i := range values {
			values[i] = escapeRegex(values[i])
		}
	}
	ci := CaseInsensitive{
		Key:   cis.Key,
		Value: fmt.Sprintf("^(%s)$", strings.Join(values, "|")),
	}
	return bson.Marshal(ci)
}

type CaseInsensitive struct {
	Key   string
	Value string
}

func (ci CaseInsensitive) MarshalBSON() ([]byte, error) {
	value := ci.Value
	if value == "" {
		value = "^$"
	} else {
		value = escapeRegex(value)
	}
	return bson.Marshal(Regex{Key: ci.Key, Value: "(?i)" + ci.Value})
}

type FilterIn struct {
	Key    string
	Values []interface{}
}

func NewFilterIn(key string, values interface{}) FilterIn {
	fi := FilterIn{Key: key}
	switch values.(type) {
	case []interface{}:
		fi.Values = values.([]interface{})
		if len(fi.Values) == 0 {
			return NewFilterIn(key, nil)
		}
	case []string:
		value := values.([]string)
		fi.Values = make([]interface{}, len(value))
		for i := range value {
			fi.Values[i] = value[i]
		}
		if len(fi.Values) == 0 {
			return NewFilterIn(key, nil)
		}
	case []int:
		value := values.([]int)
		fi.Values = make([]interface{}, len(value))
		for i := range value {
			fi.Values[i] = value[i]
		}
		if len(fi.Values) == 0 {
			return NewFilterIn(key, nil)
		}
	case nil:
		fi.Key = ""
	default:
		panic(fmt.Sprintf("cannot convert values to filter in: %T", values))
	}
	return fi
}

func (fi FilterIn) MarshalBSON() ([]byte, error) {
	return filterMarshal("$in", fi.Key, fi.Values)
}

func filterMarshal(op, key string, value interface{}) ([]byte, error) {
	if len(key) == 0 {
		return bson.Marshal(map[string]interface{}{})
	}
	m := map[string]map[string]interface{}{
		key: map[string]interface{}{
			op: value,
		},
	}
	return bson.Marshal(m)
}

func escapeRegex(s string) string {
	re := regexp.MustCompile(`[^a-zA-Z0-9]`)
	s = re.ReplaceAllString(s, `.`)
	return s
}
