/*
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>
*/

package engine

import (
	"fmt"
	"net"
	"reflect"
	"regexp"
	"strings"
	"time"

	"github.com/cgrates/birpc/context"
	"github.com/cgrates/cgrates/config"
	"github.com/cgrates/cgrates/utils"
)

// NewFilterS initializtes the filter service
func NewFilterS(cfg *config.CGRConfig, connMgr *ConnManager, dm *DataManager) (fS *FilterS) {
	fS = &FilterS{
		dm:      dm,
		cfg:     cfg,
		connMgr: connMgr,
	}
	return
}

// FilterS is a service used to take decisions in case of filters
// uses lazy connections where necessary to avoid deadlocks on service startup
type FilterS struct {
	cfg     *config.CGRConfig
	dm      *DataManager
	connMgr *ConnManager
}

// Pass will check all filters within filterIDs and require them passing for dataProvider
// there should be at least one filter passing, ie: if filters are not active event will fail to pass
// receives the event as DataProvider so we can accept undecoded data (ie: HttpRequest)
func (fS *FilterS) Pass(tenant string, filterIDs []string,
	ev utils.DataProvider) (pass bool, err error) {
	if len(filterIDs) == 0 {
		return true, nil
	}
	dDP := newDynamicDP(fS.cfg.FilterSCfg().ResourceSConns, fS.cfg.FilterSCfg().StatSConns,
		fS.cfg.FilterSCfg().ApierSConns, fS.cfg.FilterSCfg().TrendSConns, fS.cfg.FilterSCfg().RankingSConns, tenant, ev)
	for _, fltrID := range filterIDs {
		f, err := fS.dm.GetFilter(tenant, fltrID,
			true, true, utils.NonTransactional)
		if err != nil {
			if err == utils.ErrNotFound {
				err = utils.ErrPrefixNotFound(fltrID)
			}
			return false, err
		}
		if f.ActivationInterval != nil &&
			!f.ActivationInterval.IsActiveAtTime(time.Now()) { // not active
			continue
		}
		for _, fltr := range f.Rules {
			if pass, err = fltr.Pass(dDP); err != nil || !pass {
				return pass, err
			}
		}
		pass = true
	}
	return
}

// checkPrefix verify if the value has as prefix one of the prefixes
func checkPrefix(value string, prefixes []string) (hasPrefix bool) {
	for _, prefix := range prefixes {
		if strings.HasPrefix(value, prefix) {
			hasPrefix = true
			break
		}
	}
	if !hasPrefix {
		return false
	}
	return
}

// verifyPrefixes verify the Element and the Values from FilterRule if has as prefix one of the prefixes
func verifyPrefixes(rule *FilterRule, prefixes []string) (hasPrefix bool) {
	if strings.HasPrefix(rule.Element, utils.DynamicDataPrefix) {
		if hasPrefix = checkPrefix(rule.Element, prefixes); !hasPrefix {
			return
		}
	}
	for _, value := range rule.Values {
		hasPrefix = false // reset hasPrefix
		if strings.HasPrefix(value, utils.DynamicDataPrefix) {
			if hasPrefix = checkPrefix(value, prefixes); !hasPrefix {
				return
			}
		}
	}
	return true
}

// LazyPass is almost the same as Pass except that it verify if the
// Element of the Values from FilterRules has as prefix one of the pathPrfxs
func (fS *FilterS) LazyPass(tenant string, filterIDs []string,
	ev utils.DataProvider, pathPrfxs []string) (pass bool, lazyCheckRules []*FilterRule, err error) {
	if len(filterIDs) == 0 {
		return true, nil, nil
	}
	pass = true
	dDP := newDynamicDP(fS.cfg.FilterSCfg().ResourceSConns, fS.cfg.FilterSCfg().StatSConns,
		fS.cfg.FilterSCfg().ApierSConns, fS.cfg.FilterSCfg().TrendSConns, fS.cfg.FilterSCfg().RankingSConns, tenant, ev)
	for _, fltrID := range filterIDs {
		var f *Filter
		f, err = fS.dm.GetFilter(tenant, fltrID,
			true, true, utils.NonTransactional)
		if err != nil {
			if err == utils.ErrNotFound {
				err = utils.ErrPrefixNotFound(fltrID)
			}
			return
		}
		if f.ActivationInterval != nil &&
			!f.ActivationInterval.IsActiveAtTime(time.Now()) { // not active
			continue
		}

		for _, rule := range f.Rules {
			if !verifyPrefixes(rule, pathPrfxs) {
				lazyCheckRules = append(lazyCheckRules, rule)
				continue
			}
			if pass, err = rule.Pass(dDP); err != nil || !pass {
				return
			}
		}
	}
	return
}

func splitDynFltrValues(val, sep string) (vals []string) {
	startIdx := strings.IndexByte(val, utils.RSRDynStartChar)
	endIdx := strings.IndexByte(val, utils.RSRDynEndChar)
	if startIdx == -1 || endIdx == -1 {
		return strings.Split(val, sep)
	}

	vals = strings.Split(val[:startIdx], sep)
	vals[len(vals)-1] += val[startIdx : endIdx+1]
	val = val[endIdx+1:]
	if len(val) == 0 {
		return
	}
	valsEnd := splitDynFltrValues(val, sep)
	vals[len(vals)-1] += valsEnd[0]
	return append(vals, valsEnd[1:]...)
}

// NewFilterFromInline parses an inline rule into a compiled Filter
func NewFilterFromInline(tenant, inlnRule string) (f *Filter, err error) {
	ruleSplt := utils.SplitPath(inlnRule, utils.InInFieldSep[0], 3)
	if len(ruleSplt) != 3 {
		return nil, fmt.Errorf("inline parse error for string: <%s>", inlnRule)
	}
	var vals []string
	if ruleSplt[2] != utils.EmptyString {
		vals = splitDynFltrValues(ruleSplt[2], utils.PipeSep)
	}
	f = &Filter{
		Tenant: tenant,
		ID:     inlnRule,
		Rules: []*FilterRule{{
			Type:    ruleSplt[0],
			Element: ruleSplt[1],
			Values:  vals,
		}},
	}
	if err = f.Compile(); err != nil {
		return nil, err
	}
	return
}

// Filter structure to define a basic filter
type Filter struct {
	Tenant             string
	ID                 string
	Rules              []*FilterRule
	ActivationInterval *utils.ActivationInterval
}

// Clone method for Filter
func (fltr *Filter) Clone() *Filter {
	if fltr == nil {
		return nil
	}
	clone := &Filter{
		Tenant: fltr.Tenant,
		ID:     fltr.ID,
	}
	if fltr.Rules != nil {
		clone.Rules = make([]*FilterRule, len(fltr.Rules))
		for i, rule := range fltr.Rules {
			clone.Rules[i] = rule.Clone()
		}
	}
	if fltr.ActivationInterval != nil {
		clone.ActivationInterval = fltr.ActivationInterval.Clone()
	}
	return clone
}

// CacheClone returns a clone of Filter used by ltcache CacheCloner
func (fltr *Filter) CacheClone() any {
	return fltr.Clone()
}

// FilterWithOpts the arguments for the replication
type FilterWithAPIOpts struct {
	*Filter
	APIOpts map[string]any
}

// TenantID returns the tenant wit the ID
func (fltr *Filter) TenantID() string {
	return utils.ConcatenatedKey(fltr.Tenant, fltr.ID)
}

// Compile will compile the underlaying request filters where necessary (ie. regexp rules)
func (fltr *Filter) Compile() (err error) {
	for _, rf := range fltr.Rules {
		if err = rf.CompileValues(); err != nil {
			return
		}
	}
	return
}

var supportedFiltersType utils.StringSet = utils.NewStringSet([]string{
	utils.MetaString, utils.MetaContains, utils.MetaPrefix, utils.MetaSuffix,
	utils.MetaTimings, utils.MetaRSR, utils.MetaDestinations, utils.MetaHTTP,
	utils.MetaEmpty, utils.MetaExists, utils.MetaLessThan, utils.MetaLessOrEqual,
	utils.MetaGreaterThan, utils.MetaGreaterOrEqual, utils.MetaEqual,
	utils.MetaIPNet, utils.MetaAPIBan, utils.MetaSentryPeer, utils.MetaActivationInterval,
	utils.MetaRegex})
var needsFieldName utils.StringSet = utils.NewStringSet([]string{
	utils.MetaString, utils.MetaContains, utils.MetaPrefix, utils.MetaSuffix,
	utils.MetaTimings, utils.MetaRSR, utils.MetaDestinations, utils.MetaLessThan,
	utils.MetaEmpty, utils.MetaExists, utils.MetaLessOrEqual, utils.MetaGreaterThan,
	utils.MetaGreaterOrEqual, utils.MetaEqual, utils.MetaIPNet, utils.MetaAPIBan, utils.MetaSentryPeer,
	utils.MetaActivationInterval,
	utils.MetaRegex})
var needsValues utils.StringSet = utils.NewStringSet([]string{utils.MetaString, utils.MetaContains, utils.MetaPrefix,
	utils.MetaSuffix, utils.MetaTimings, utils.MetaRSR, utils.MetaDestinations,
	utils.MetaLessThan, utils.MetaLessOrEqual, utils.MetaGreaterThan, utils.MetaGreaterOrEqual,
	utils.MetaEqual, utils.MetaIPNet, utils.MetaAPIBan, utils.MetaSentryPeer, utils.MetaActivationInterval,
	utils.MetaRegex})

// NewFilterRule returns a new filter
func NewFilterRule(rfType, fieldName string, vals []string) (*FilterRule, error) {
	var negative bool
	rType := rfType
	if strings.HasPrefix(rfType, utils.MetaNot) {
		rType = utils.Meta + strings.TrimPrefix(rfType, utils.MetaNot)
		negative = true
	}
	if strings.HasPrefix(rfType, utils.MetaHTTP) {
		rType = utils.MetaHTTP
	}
	if !supportedFiltersType.Has(rType) {
		return nil, fmt.Errorf("Unsupported filter Type: %s", rfType)
	}
	if fieldName == "" && needsFieldName.Has(rType) {
		return nil, fmt.Errorf("Element is mandatory for Type: %s", rfType)
	}
	if len(vals) == 0 && needsValues.Has(rType) {
		return nil, fmt.Errorf("Values is mandatory for Type: %s", rfType)
	}
	rf := &FilterRule{
		Type:     rfType,
		Element:  fieldName,
		Values:   vals,
		negative: utils.BoolPointer(negative),
	}
	if err := rf.CompileValues(); err != nil {
		return nil, err
	}
	return rf, nil
}

// FilterRule filters requests coming into various places
// Pass rule: default negative, one matching rule should pass the filter
type FilterRule struct {
	Type        string            // Filter type (*string, *timing, *rsr_filters, *stats, *lt, *lte, *gt, *gte)
	Element     string            // Name of the field providing us the Values to check (used in case of some )
	Values      []string          // Filter definition
	rsrValues   config.RSRParsers // Cache here the
	rsrElement  *config.RSRParser // Cache here the
	rsrFilters  utils.RSRFilters  // Cache here the RSRFilter Values
	regexValues []*regexp.Regexp
	negative    *bool
}

// Clone method for FilterRule
func (fltr *FilterRule) Clone() *FilterRule {
	if fltr == nil {
		return nil
	}
	clone := &FilterRule{
		Type:    fltr.Type,
		Element: fltr.Element,
	}
	if fltr.Values != nil {
		clone.Values = make([]string, len(fltr.Values))
		copy(clone.Values, fltr.Values)
	}
	if fltr.rsrValues != nil {
		clone.rsrValues = make(config.RSRParsers, len(fltr.rsrValues))
		copy(clone.rsrValues, fltr.rsrValues)
	}
	if fltr.negative != nil {
		clone.negative = new(bool)
		*clone.negative = *fltr.negative
	}
	if fltr.rsrFilters != nil {
		fltr.rsrFilters = make(utils.RSRFilters, len(fltr.rsrFilters))
		for _, filter := range fltr.rsrFilters {
			clone.rsrFilters = append(clone.rsrFilters, filter.Clone())
		}
	}
	if fltr.regexValues != nil {
		clone.regexValues = make([]*regexp.Regexp, len(fltr.regexValues))
		for i, regex := range fltr.regexValues {
			clone.regexValues[i] = regex.Copy()
		}
	}
	return clone
}

// CompileValues compiles RSR fields
func (fltr *FilterRule) CompileValues() (err error) {
	switch fltr.Type {
	case utils.MetaRegex, utils.MetaNotRegex:
		fltr.regexValues = make([]*regexp.Regexp, len(fltr.Values))
		for i, val := range fltr.Values {
			if fltr.regexValues[i], err = regexp.Compile(val); err != nil {
				return
			}
		}
	case utils.MetaRSR, utils.MetaNotRSR:
		if fltr.rsrFilters, err = utils.ParseRSRFiltersFromSlice(fltr.Values); err != nil {
			return
		}
	case utils.MetaExists, utils.MetaNotExists, utils.MetaEmpty, utils.MetaNotEmpty: // only the element is builded
	case utils.MetaActivationInterval, utils.MetaNotActivationInterval:
		fltr.rsrValues = make(config.RSRParsers, len(fltr.Values))
		for i, strVal := range fltr.Values {
			if fltr.rsrValues[i], err = config.NewRSRParser(strVal); err != nil {
				return
			}
		}
	default:
		if fltr.rsrValues, err = config.NewRSRParsersFromSlice(fltr.Values); err != nil {
			return
		}
	}
	if fltr.rsrElement, err = config.NewRSRParser(fltr.Element); err != nil {
		return
	} else if fltr.rsrElement == nil {
		return fmt.Errorf("empty RSRParser in rule: <%s>", fltr.Element)
	}
	return
}

// Pass is the method which should be used from outside.
func (fltr *FilterRule) Pass(dDP utils.DataProvider) (result bool, err error) {
	if fltr.negative == nil {
		fltr.negative = utils.BoolPointer(strings.HasPrefix(fltr.Type, utils.MetaNot))
	}

	switch fltr.Type {
	case utils.MetaString, utils.MetaNotString, utils.MetaNIString:
		result, err = fltr.passString(dDP)
	case utils.MetaContains, utils.MetaNotContains:
		result, err = fltr.passContains(dDP)
	case utils.MetaEmpty, utils.MetaNotEmpty:
		result, err = fltr.passEmpty(dDP)
	case utils.MetaExists, utils.MetaNotExists, utils.MetaNIExists:
		result, err = fltr.passExists(dDP)
	case utils.MetaPrefix, utils.MetaNotPrefix, utils.MetaNIPrefix:
		result, err = fltr.passStringPrefix(dDP)
	case utils.MetaSuffix, utils.MetaNotSuffix, utils.MetaNISuffix:
		result, err = fltr.passStringSuffix(dDP)
	case utils.MetaTimings, utils.MetaNotTimings:
		result, err = fltr.passTimings(dDP)
	case utils.MetaDestinations, utils.MetaNotDestinations:
		result, err = fltr.passDestinations(dDP)
	case utils.MetaRSR, utils.MetaNotRSR:
		result, err = fltr.passRSR(dDP)
	case utils.MetaLessThan, utils.MetaLessOrEqual, utils.MetaGreaterThan, utils.MetaGreaterOrEqual:
		result, err = fltr.passGreaterThan(dDP)
	case utils.MetaEqual, utils.MetaNotEqual:
		result, err = fltr.passEqualTo(dDP)
	case utils.MetaIPNet, utils.MetaNotIPNet:
		result, err = fltr.passIPNet(dDP)
	case utils.MetaAPIBan, utils.MetaNotAPIBan:
		result, err = fltr.passAPIBan(dDP)
	case utils.MetaSentryPeer, utils.MetaNotSentryPeer:
		result, err = fltr.passSentryPeer(dDP)
	case utils.MetaActivationInterval, utils.MetaNotActivationInterval:
		result, err = fltr.passActivationInterval(dDP)
	case utils.MetaRegex, utils.MetaNotRegex:
		result, err = fltr.passRegex(dDP)
	default:
		if strings.HasPrefix(fltr.Type, utils.MetaHTTP) && strings.Index(fltr.Type, "#") == len(utils.MetaHTTP) {
			result, err = fltr.passHttp(dDP)
			break
		}
		err = utils.ErrPrefixNotErrNotImplemented(fltr.Type)
	}
	if err != nil {
		return false, err
	}
	return result != *(fltr.negative), nil
}

func (fltr *FilterRule) passString(dDP utils.DataProvider) (bool, error) {
	strVal, err := fltr.rsrElement.ParseDataProvider(dDP)
	if err != nil {
		if err == utils.ErrNotFound {
			return false, nil
		}
		return false, err
	}
	for _, val := range fltr.rsrValues {
		sval, err := val.ParseDataProvider(dDP)
		if err != nil {
			continue
		}
		if strVal == sval {
			return true, nil
		}
	}
	return false, nil
}

func (fltr *FilterRule) passContains(dDP utils.DataProvider) (bool, error) {
	strVal, err := fltr.rsrElement.ParseDataProvider(dDP)
	if err != nil {
		if err == utils.ErrNotFound {
			return false, nil
		}
		return false, err
	}
	for _, val := range fltr.rsrValues {
		sval, err := val.ParseDataProvider(dDP)
		if err != nil {
			continue
		}
		if strings.Contains(strVal, sval) {
			return true, nil
		}
	}
	return false, nil
}

func (fltr *FilterRule) passExists(dDP utils.DataProvider) (bool, error) {
	path, err := fltr.rsrElement.CompileDynRule(dDP)
	if err != nil {
		return false, err
	}
	if _, err := utils.DPDynamicInterface(path, dDP); err != nil {
		if err == utils.ErrNotFound {
			return false, nil
		}
		return false, err
	}
	return true, nil
}

func (fltr *FilterRule) passEmpty(dDP utils.DataProvider) (bool, error) {
	path, err := fltr.rsrElement.CompileDynRule(dDP)
	if err != nil {
		return false, err
	}
	val, err := utils.DPDynamicInterface(path, dDP)
	if err != nil {
		if err == utils.ErrNotFound {
			return true, nil
		}
		return false, err
	}
	if val == nil {
		return true, nil
	}
	rval := reflect.ValueOf(val)
	if rval.Type().Kind() == reflect.Ptr {
		if rval.IsNil() {
			return true, nil
		}
		rval = rval.Elem()
	}
	switch rval.Type().Kind() {
	case reflect.String:
		return rval.Interface() == "", nil
	case reflect.Slice:
		return rval.Len() == 0, nil
	case reflect.Map:
		return len(rval.MapKeys()) == 0, nil
	default:
		return false, nil
	}
}

func (fltr *FilterRule) passStringPrefix(dDP utils.DataProvider) (bool, error) {
	strVal, err := fltr.rsrElement.ParseDataProvider(dDP)
	if err != nil {
		if err == utils.ErrNotFound {
			return false, nil
		}
		return false, err
	}
	for _, prfxVal := range fltr.rsrValues {
		prfx, err := prfxVal.ParseDataProvider(dDP)
		if err != nil {
			continue
		}
		if strings.HasPrefix(strVal, prfx) {
			return true, nil
		}
	}
	return false, nil
}

func (fltr *FilterRule) passStringSuffix(dDP utils.DataProvider) (bool, error) {
	strVal, err := fltr.rsrElement.ParseDataProvider(dDP)
	if err != nil {
		if err == utils.ErrNotFound {
			return false, nil
		}
		return false, err
	}
	for _, prfxVal := range fltr.rsrValues {
		prfx, err := prfxVal.ParseDataProvider(dDP)
		if err != nil {
			continue
		}
		if strings.HasSuffix(strVal, prfx) {
			return true, nil
		}
	}
	return false, nil
}

func (fltr *FilterRule) passTimings(dDP utils.DataProvider) (bool, error) {
	tmVal, err := fltr.rsrElement.ParseDataProviderWithInterfaces(dDP)
	if err != nil {
		if err == utils.ErrNotFound {
			return false, nil
		}
		return false, err
	}

	tmTime, err := utils.IfaceAsTime(tmVal, config.CgrConfig().GeneralCfg().DefaultTimezone)
	if err != nil {
		return false, err
	}

	for _, valTmIDVal := range fltr.rsrValues {
		valTmID, err := valTmIDVal.ParseDataProvider(dDP)
		if err != nil {
			return false, err
		}
		var tm utils.TPTiming
		if err = connMgr.Call(context.TODO(), config.CgrConfig().FilterSCfg().ApierSConns,
			utils.APIerSv1GetTiming,
			&utils.ArgsGetTimingID{ID: valTmID}, &tm); err != nil {
			if err.Error() != utils.ErrNotFound.Error() {
				return false, err
			}
			continue
		}
		ritm := &RITiming{
			ID:        tm.ID,
			Years:     tm.Years,
			Months:    tm.Months,
			MonthDays: tm.MonthDays,
			WeekDays:  tm.WeekDays,
			StartTime: tm.StartTime,
			EndTime:   tm.EndTime,
		}
		if ritm.IsActiveAt(tmTime) {
			return true, nil
		}
	}
	return false, nil
}

func (fltr *FilterRule) passDestinations(dDP utils.DataProvider) (bool, error) {
	dst, err := fltr.rsrElement.ParseDataProvider(dDP)
	if err != nil {
		if err == utils.ErrNotFound {
			return false, nil
		}
		return false, err
	}
	// iterate the DestinationIDs gotten from filter values, and try to get their prefixes from DataDB
	for _, valDstIDVal := range fltr.rsrValues {
		valDstID, err := valDstIDVal.ParseDataProvider(dDP)
		if err != nil {
			continue
		}
		var rplDest Destination
		if err = connMgr.Call(context.TODO(), config.CgrConfig().FilterSCfg().ApierSConns,
			utils.APIerSv1GetDestination,
			&valDstID, &rplDest); err != nil {
			if err.Error() == utils.ErrNotFound.Error() {
				continue // if destination not found, continue on next filter value
			}
			return false, err
		}
		// return true if any prefixes found from GetDestination match the destination gotten from filter element
		for _, rplPrfx := range rplDest.Prefixes {
			if strings.HasPrefix(dst, rplPrfx) {
				return true, nil
			}
		}
	}
	return false, err
}

func (fltr *FilterRule) passRSR(dDP utils.DataProvider) (bool, error) {
	fld, err := fltr.rsrElement.ParseDataProvider(dDP)
	if err != nil {
		if err == utils.ErrNotFound {
			match := fltr.rsrFilters.FilterRules() == "^$"
			return match, nil
		}
		return false, err
	}
	match := fltr.rsrFilters.Pass(fld, false)
	return match, nil
}

func (fltr *FilterRule) passGreaterThan(dDP utils.DataProvider) (bool, error) {
	fldStr, err := fltr.rsrElement.ParseDataProviderWithInterfaces(dDP)
	if err != nil {
		if err == utils.ErrNotFound {
			return false, nil
		}
		return false, err
	}
	fldIf := utils.StringToInterface(fldStr)
	orEqual := fltr.Type == utils.MetaGreaterOrEqual ||
		fltr.Type == utils.MetaLessThan
	for _, val := range fltr.rsrValues {
		sval, err := val.ParseDataProviderWithInterfaces(dDP)
		if err != nil {
			continue
		}
		if gte, err := utils.GreaterThan(fldIf, utils.StringToInterface(sval), orEqual); err != nil {
			return false, err
		} else if (utils.MetaGreaterThan == fltr.Type || utils.MetaGreaterOrEqual == fltr.Type) && gte {
			return true, nil
		} else if (utils.MetaLessThan == fltr.Type || utils.MetaLessOrEqual == fltr.Type) && !gte {
			return true, nil
		}
	}
	return false, nil
}

func (fltr *FilterRule) passEqualTo(dDP utils.DataProvider) (bool, error) {
	fldStr, err := fltr.rsrElement.ParseDataProviderWithInterfaces(dDP)
	if err != nil {
		if err == utils.ErrNotFound {
			return false, nil
		}
		return false, err
	}
	fldIf := utils.StringToInterface(fldStr)
	for _, val := range fltr.rsrValues {
		sval, err := val.ParseDataProviderWithInterfaces(dDP)
		if err != nil {
			continue
		}
		if eq, err := utils.EqualTo(fldIf, utils.StringToInterface(sval)); err != nil {
			return false, err
		} else if eq {
			return true, nil
		}
	}
	return false, nil
}

func (fltr *FilterRule) passIPNet(dDP utils.DataProvider) (bool, error) {
	strVal, err := fltr.rsrElement.ParseDataProvider(dDP)
	if err != nil {
		if err == utils.ErrNotFound {
			return false, nil
		}
		return false, err
	}
	ip := net.ParseIP(strVal)
	if ip == nil {
		return false, nil
	}

	for _, val := range fltr.rsrValues {
		sval, err := val.ParseDataProvider(dDP)
		if err != nil {
			continue
		}
		_, ipNet, err := net.ParseCIDR(sval)
		if err != nil {
			continue
		}
		if ipNet.Contains(ip) {
			return true, nil
		}
	}
	return false, nil
}

func (fltr *FilterRule) passAPIBan(dDP utils.DataProvider) (bool, error) {
	strVal, err := fltr.rsrElement.ParseDataProvider(dDP)
	if err != nil {
		if err == utils.ErrNotFound {
			return false, nil
		}
		return false, err
	}
	if fltr.Values[0] != utils.MetaAll &&
		fltr.Values[0] != utils.MetaSingle { // force only valid values
		return false, fmt.Errorf("invalid value for apiban filter: <%s>", fltr.Values[0])
	}
	return dm.GetAPIBan(strVal, config.CgrConfig().APIBanCfg().Keys, fltr.Values[0] != utils.MetaAll, true, true)
}

func (fltr *FilterRule) passSentryPeer(dDP utils.DataProvider) (bool, error) {
	strVal, err := fltr.rsrElement.ParseDataProvider(dDP)
	if err != nil {
		if err == utils.ErrNotFound {
			return false, nil
		}
		return false, err
	}
	if fltr.Values[0] != utils.MetaNumber && fltr.Values[0] != utils.MetaIp {
		return false, fmt.Errorf("invalid value for sentrypeer filter: <%s>", fltr.Values[0])
	}
	return GetSentryPeer(strVal, config.CgrConfig().SentryPeerCfg(), fltr.Values[0])
}

func parseTime(rsr *config.RSRParser, dDp utils.DataProvider) (_ time.Time, err error) {
	var str string
	if str, err = rsr.ParseDataProvider(dDp); err != nil {
		return
	}
	return utils.ParseTimeDetectLayout(str, config.CgrConfig().GeneralCfg().DefaultTimezone)
}

func (fltr *FilterRule) passActivationInterval(dDp utils.DataProvider) (bool, error) {
	timeVal, err := parseTime(fltr.rsrElement, dDp)
	if err != nil {
		if err == utils.ErrNotFound {
			return false, nil
		}
		return false, err
	}

	if len(fltr.rsrValues) == 2 {
		endTime, err := parseTime(fltr.rsrValues[1], dDp)
		if err != nil {
			return false, err
		}
		if fltr.rsrValues[0] == nil {
			return timeVal.Before(endTime), nil
		}
		startTime, err := parseTime(fltr.rsrValues[0], dDp)
		if err != nil {
			return false, err
		}
		return startTime.Before(timeVal) && timeVal.Before(endTime), nil
	}
	startTime, err := parseTime(fltr.rsrValues[0], dDp)
	if err != nil {
		return false, err
	}
	return startTime.Before(timeVal), nil
}

func verifyInlineFilterS(fltrs []string) (err error) {
	for _, fl := range fltrs {
		if strings.HasPrefix(fl, utils.Meta) {
			if _, err = NewFilterFromInline(utils.EmptyString, fl); err != nil {
				return
			}
		}
	}
	return
}

func CheckFilter(fltr *Filter) (err error) {
	for _, rls := range fltr.Rules {
		valFunc := utils.IsPathValid
		if rls.Type == utils.MetaEmpty || rls.Type == utils.MetaExists {
			valFunc = utils.IsPathValidForExporters
		}
		if err = valFunc(rls.Element); err != nil {
			return fmt.Errorf("%s for filter <%v>", err, fltr) //encapsulated error
		}
		for _, val := range rls.Values {
			if rls.Type == utils.MetaEmpty || rls.Type == utils.MetaNotEmpty ||
				rls.Type == utils.MetaExists || rls.Type == utils.MetaNotExists &&
				val != utils.EmptyString {
				return fmt.Errorf("value of filter <%s> is not empty <%s>",
					fltr.ID, val)
			}
			if err = valFunc(val); err != nil {
				return fmt.Errorf("%s for filter <%v>", err, fltr) //encapsulated error
			}
		}
	}
	return nil
}

func (fltr *FilterRule) passRegex(dDP utils.DataProvider) (bool, error) {
	strVal, err := fltr.rsrElement.ParseDataProvider(dDP)
	if err != nil {
		if err == utils.ErrNotFound {
			return false, nil
		}
		return false, err
	}
	for _, val := range fltr.regexValues {
		if val.MatchString(strVal) {
			return true, nil
		}
	}
	return false, nil
}

func (fltr *FilterRule) passHttp(dDP utils.DataProvider) (bool, error) {
	strVal, err := fltr.rsrElement.ParseDataProvider(dDP)
	if err != nil {
		if err == utils.ErrNotFound {
			return false, nil
		}
		return false, err
	}
	return filterHTTP(fltr.Type, dDP, fltr.Element, strVal)

}

// Will return all items the element, e.g.  "~*req", "cost_details", "Charges[0]". "RatingID"
func (fltr *FilterRule) ElementItems() []string {
	return strings.Split(fltr.Element, utils.NestingSep)
}

// Creates mysql conditions used in WHERE statement out of filters
func (fltr *FilterRule) FilterToSQLQuery() (conditions []string) {
	var firstItem string   // Excluding ~*req, hold the first item of an element, left empty if no more than 1 item in element. e.g. "cost_details" out of ~*req.cost_details.Charges[0].RatingID or "" out of ~*req.answer_time
	var restOfItems string // Excluding ~*req, hold the rest of the items past the first one. If only 1 item in all element, holds that item. e.g. "Charges[0].RatingID" out of ~*req.cost_details.Charges[0].RatingID or "answer_time" out of ~*req.answer_time
	not := strings.HasPrefix(fltr.Type, utils.MetaNot)
	elementItems := fltr.ElementItems()[1:] // exclude first item: ~*req
	for i := range elementItems {           // encapsulate with "" strings starting with *
		if strings.HasPrefix(elementItems[i], utils.Meta) {
			elementItems[i] = "\"" + elementItems[i] + "\""
		}
	}
	if len(elementItems) > 1 {
		firstItem = elementItems[0]
		restOfItems = strings.Join(elementItems[1:], utils.NestingSep)
	} else {
		restOfItems = elementItems[0]
	}

	// here are for the filters that their values are empty: *exists, *notexists, *empty, *notempty..
	if len(fltr.Values) == 0 {
		switch fltr.Type {
		case utils.MetaExists, utils.MetaNotExists:
			if not { // not existing means Column IS NULL
				if firstItem == utils.EmptyString {
					conditions = append(conditions, fmt.Sprintf("%s IS NULL", restOfItems))
					return
				}
				queryPart := fmt.Sprintf("JSON_VALUE(%s, '$.%s') IS NULL", firstItem, restOfItems)
				if strings.HasPrefix(restOfItems, `"*`) {
					queryPart = fmt.Sprintf("JSON_UNQUOTE(%s)", queryPart)
				}
				conditions = append(conditions, queryPart)
				return
			}
			// existing means Column IS NOT NULL
			if firstItem == utils.EmptyString {
				conditions = append(conditions, fmt.Sprintf("%s IS NOT NULL", restOfItems))
				return
			}
			queryPart := fmt.Sprintf("JSON_VALUE(%s, '$.%s') IS NOT NULL", firstItem, restOfItems)
			if strings.HasPrefix(restOfItems, `"*`) {
				queryPart = fmt.Sprintf("JSON_UNQUOTE(%s)", queryPart)
			}
			conditions = append(conditions, queryPart)
		case utils.MetaEmpty, utils.MetaNotEmpty:
			if not {
				if firstItem == utils.EmptyString {
					conditions = append(conditions, fmt.Sprintf("%s != ''", restOfItems))
					return
				}
				queryPart := fmt.Sprintf("JSON_VALUE(%s, '$.%s') != ''", firstItem, restOfItems)
				if strings.HasPrefix(restOfItems, `"*`) {
					queryPart = fmt.Sprintf("JSON_UNQUOTE(%s)", queryPart)
				}
				conditions = append(conditions, queryPart)
				return
			}
			if firstItem == utils.EmptyString {
				conditions = append(conditions, fmt.Sprintf("%s == ''", restOfItems))
				return
			}
			queryPart := fmt.Sprintf("JSON_VALUE(%s, '$.%s') == ''", firstItem, restOfItems)
			if strings.HasPrefix(restOfItems, `"*`) {
				queryPart = fmt.Sprintf("JSON_UNQUOTE(%s)", queryPart)
			}
			conditions = append(conditions, queryPart)
		}
		return
	}
	// here are for the filters that can have more than one value: *string, *prefix, *suffix ..
	for _, value := range fltr.Values {
		switch value { // in case we have boolean values, it should be queried over 1 or 0
		case "true":
			value = "1"
		case "false":
			value = "0"
		}
		var singleCond string
		switch fltr.Type {
		case utils.MetaString, utils.MetaNotString, utils.MetaEqual, utils.MetaNotEqual:
			if not {
				if firstItem == utils.EmptyString {
					conditions = append(conditions, fmt.Sprintf("%s != '%s'", restOfItems, value))
					continue
				}
				queryPart := fmt.Sprintf("JSON_VALUE(%s, '$.%s') != '%s'",
					firstItem, restOfItems, value)
				if strings.HasPrefix(restOfItems, `"*`) {
					queryPart = fmt.Sprintf("JSON_UNQUOTE(%s)", queryPart)
				}
				conditions = append(conditions, queryPart)
				continue
			}
			if firstItem == utils.EmptyString {
				singleCond = fmt.Sprintf("%s = '%s'", restOfItems, value)
			} else {
				queryPart := fmt.Sprintf("JSON_VALUE(%s, '$.%s') = '%s'", firstItem, restOfItems, value)
				if strings.HasPrefix(restOfItems, `"*`) {
					queryPart = fmt.Sprintf("JSON_UNQUOTE(%s)", queryPart)
				}
				singleCond = queryPart
			}
		case utils.MetaLessThan, utils.MetaLessOrEqual, utils.MetaGreaterThan, utils.MetaGreaterOrEqual:
			parsedValAny := utils.StringToInterface(value)
			switch fltr.Type {
			case utils.MetaGreaterOrEqual:
				if firstItem == utils.EmptyString {
					singleCond = fmt.Sprintf("%s >= '%v'", restOfItems, parsedValAny)
				} else {
					queryPart := fmt.Sprintf("JSON_VALUE(%s, '$.%s') >= '%v'", firstItem, restOfItems, parsedValAny)
					if strings.HasPrefix(restOfItems, `"*`) {
						queryPart = fmt.Sprintf("JSON_UNQUOTE(%s)", queryPart)
					}
					singleCond = queryPart
				}
			case utils.MetaGreaterThan:
				if firstItem == utils.EmptyString {
					singleCond = fmt.Sprintf("%s > '%v'", restOfItems, parsedValAny)
				} else {
					queryPart := fmt.Sprintf("JSON_VALUE(%s, '$.%s') > '%v'", firstItem, restOfItems, parsedValAny)
					if strings.HasPrefix(restOfItems, `"*`) {
						queryPart = fmt.Sprintf("JSON_UNQUOTE(%s)", queryPart)
					}
					singleCond = queryPart
				}
			case utils.MetaLessOrEqual:
				if firstItem == utils.EmptyString {
					singleCond = fmt.Sprintf("%s <= '%v'", restOfItems, parsedValAny)
				} else {
					queryPart := fmt.Sprintf("JSON_VALUE(%s, '$.%s') <= '%v'", firstItem, restOfItems, parsedValAny)
					if strings.HasPrefix(restOfItems, `"*`) {
						queryPart = fmt.Sprintf("JSON_UNQUOTE(%s)", queryPart)
					}
					singleCond = queryPart
				}
			case utils.MetaLessThan:
				if firstItem == utils.EmptyString {
					singleCond = fmt.Sprintf("%s < '%v'", restOfItems, parsedValAny)
				} else {
					queryPart := fmt.Sprintf("JSON_VALUE(%s, '$.%s') < '%v'", firstItem, restOfItems, parsedValAny)
					if strings.HasPrefix(restOfItems, `"*`) {
						queryPart = fmt.Sprintf("JSON_UNQUOTE(%s)", queryPart)
					}
					singleCond = queryPart
				}
			}
		case utils.MetaPrefix, utils.MetaNotPrefix:
			if not {
				if firstItem == utils.EmptyString {
					conditions = append(conditions, fmt.Sprintf("%s NOT LIKE '%s%%'", restOfItems, value))
					continue
				}
				queryPart := fmt.Sprintf("JSON_VALUE(%s, '$.%s') NOT LIKE '%s%%'", firstItem, restOfItems, value)
				if strings.HasPrefix(restOfItems, `"*`) {
					queryPart = fmt.Sprintf("JSON_UNQUOTE(%s)", queryPart)
				}
				conditions = append(conditions, queryPart)
				continue
			}
			if firstItem == utils.EmptyString {
				singleCond = fmt.Sprintf("%s LIKE '%s%%'", restOfItems, value)
			} else {
				queryPart := fmt.Sprintf("JSON_VALUE(%s, '$.%s') LIKE '%s%%'", firstItem, restOfItems, value)
				if strings.HasPrefix(restOfItems, `"*`) {
					queryPart = fmt.Sprintf("JSON_UNQUOTE(%s)", queryPart)
				}
				singleCond = queryPart
			}
		case utils.MetaSuffix, utils.MetaNotSuffix:
			if not {
				if firstItem == utils.EmptyString {
					conditions = append(conditions, fmt.Sprintf("%s NOT LIKE '%%%s'", restOfItems, value))
					continue
				}
				queryPart := fmt.Sprintf("JSON_VALUE(%s, '$.%s') NOT LIKE '%%%s'", firstItem, restOfItems, value)
				if strings.HasPrefix(restOfItems, `"*`) {
					queryPart = fmt.Sprintf("JSON_UNQUOTE(%s)", queryPart)
				}
				conditions = append(conditions, queryPart)
				continue
			}
			if firstItem == utils.EmptyString {
				singleCond = fmt.Sprintf("%s LIKE '%%%s'", restOfItems, value)
			} else {
				queryPart := fmt.Sprintf("JSON_VALUE(%s, '$.%s') LIKE '%%%s'", firstItem, restOfItems, value)
				if strings.HasPrefix(restOfItems, `"*`) {
					queryPart = fmt.Sprintf("JSON_UNQUOTE(%s)", queryPart)
				}
				singleCond = queryPart
			}
		case utils.MetaRegex, utils.MetaNotRegex:
			if not {
				if firstItem == utils.EmptyString {
					conditions = append(conditions, fmt.Sprintf("%s NOT REGEXP '%s'", restOfItems, value))
					continue
				}
				queryPart := fmt.Sprintf("JSON_VALUE(%s, '$.%s') NOT REGEXP '%s'", firstItem, restOfItems, value)
				if strings.HasPrefix(restOfItems, `"*`) {
					queryPart = fmt.Sprintf("JSON_UNQUOTE(%s)", queryPart)
				}
				conditions = append(conditions, queryPart)
				continue
			}
			if firstItem == utils.EmptyString {
				singleCond = fmt.Sprintf("%s REGEXP '%s'", restOfItems, value)
			} else {
				queryPart := fmt.Sprintf("JSON_VALUE(%s, '$.%s') REGEXP '%s'", firstItem, restOfItems, value)
				if strings.HasPrefix(restOfItems, `"*`) {
					queryPart = fmt.Sprintf("JSON_UNQUOTE(%s)", queryPart)
				}
				singleCond = queryPart
			}
		}
		conditions = append(conditions, singleCond)
	}
	return
}
