//go:build integration
// +build integration

/*
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 general_tests

import (
	"path"
	"reflect"
	"sort"
	"testing"
	"time"

	"github.com/cgrates/birpc"
	"github.com/cgrates/birpc/context"
	"github.com/cgrates/cgrates/attributes"
	"github.com/cgrates/cgrates/chargers"
	"github.com/cgrates/cgrates/config"
	"github.com/cgrates/cgrates/engine"
	"github.com/cgrates/cgrates/loaders"
	"github.com/cgrates/cgrates/utils"
)

var (
	fltrCfgPath string
	fltrCfg     *config.CGRConfig
	fltrRpc     *birpc.Client
	fltrConfDIR string //run tests for specific configuration
	fltrDelay   int

	sTestsFltr = []func(t *testing.T){
		testV1FltrLoadConfig,
		testV1FltrFlushDBs,
		testV1FltrStartEngine,
		testV1FltrRpcConn,
		testV1FltrLoadTarrifPlans,
		testV1FltrAddStats,
		testV1FltrPopulateThreshold,
		testV1FltrGetThresholdForEvent,
		testV1FltrGetThresholdForEvent2,
		testV1FltrPopulateResources,
		testV1FltrPopulateResourcesAvailableUnits,
		testV1FltrAccounts,
		testV1FltrAccountsExistsDynamicaly,
		testV1FltrAttributesPrefix,
		testV1FltrFlushDBs,
		testV1FltrChargerSuffix,
		testV1FltrStopEngine,
	}
)

// Test start here
func TestFltrIT(t *testing.T) {
	switch *utils.DBType {
	case utils.MetaInternal:
		fltrConfDIR = "filters_internal"
	case utils.MetaRedis:
		fltrConfDIR = "filters_redis"
	case utils.MetaMySQL:
		fltrConfDIR = "filters_mysql"
	case utils.MetaMongo:
		fltrConfDIR = "filters_mongo"
	case utils.MetaPostgres:
		fltrConfDIR = "filters_postgres"
	default:
		t.Fatal("Unknown Database type")
	}

	for _, stest := range sTestsFltr {
		t.Run(fltrConfDIR, stest)
	}
}

func testV1FltrLoadConfig(t *testing.T) {
	var err error
	fltrCfgPath = path.Join(*utils.DataDir, "conf", "samples", fltrConfDIR)
	if *utils.Encoding == utils.MetaGOB {
		cdrsCfgPath = path.Join(*utils.DataDir, "conf", "samples", fltrConfDIR+"_gob")
	}
	if fltrCfg, err = config.NewCGRConfigFromPath(context.Background(), fltrCfgPath); err != nil {
		t.Error(err)
	}
	fltrDelay = 1000
}

func testV1FltrFlushDBs(t *testing.T) {
	if err := engine.InitDB(fltrCfg); err != nil {
		t.Fatal(err)
	}
}

func testV1FltrStartEngine(t *testing.T) {
	if _, err := engine.StopStartEngine(fltrCfgPath, fltrDelay); err != nil {
		t.Fatal(err)
	}
}

func testV1FltrRpcConn(t *testing.T) {
	fltrRpc = engine.NewRPCClient(t, fltrCfg.ListenCfg(), *utils.Encoding)
}

func testV1FltrLoadTarrifPlans(t *testing.T) {

	caching := utils.MetaReload
	for _, dbc := range fltrCfg.DbCfg().DBConns {
		if dbc.Type == utils.MetaInternal {
			caching = utils.MetaNone
		}
	}
	var reply string
	if err := fltrRpc.Call(context.Background(), utils.LoaderSv1Run,
		&loaders.ArgsProcessFolder{
			APIOpts: map[string]any{
				utils.MetaStopOnError: true,
				utils.MetaCache:       caching,
			},
		}, &reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Error("Unexpected reply returned:", reply)
	}
	time.Sleep(200 * time.Millisecond)
}

func testV1FltrAddStats(t *testing.T) {
	var reply []string
	expected := []string{"Stat_1"}
	ev1 := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "event1",
		Event: map[string]any{
			utils.AccountField: "1001",
		},
		APIOpts: map[string]any{
			utils.MetaUsage:     11 * time.Second,
			utils.MetaStartTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC),
			utils.MetaCost:      10.0,
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.StatSv1ProcessEvent, &ev1, &reply); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(reply, expected) {
		t.Errorf("Expecting: %+v, received: %+v", expected, reply)
	}

	expected = []string{"Stat_1"}
	ev1 = &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "event2",
		Event: map[string]any{
			utils.AccountField: "1001",
		},
		APIOpts: map[string]any{
			utils.MetaUsage:     11 * time.Second,
			utils.MetaStartTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC),
			utils.MetaCost:      10.5,
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.StatSv1ProcessEvent, &ev1, &reply); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(reply, expected) {
		t.Errorf("Expecting: %+v, received: %+v", expected, reply)
	}

	expected = []string{"Stat_2"}
	ev1 = &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "event2",
		Event: map[string]any{
			utils.AccountField: "1002",
		},
		APIOpts: map[string]any{
			utils.MetaUsage:     5 * time.Second,
			utils.MetaStartTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC),
			utils.MetaCost:      12.5,
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.StatSv1ProcessEvent, &ev1, &reply); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(reply, expected) {
		t.Errorf("Expecting: %+v, received: %+v", expected, reply)
	}

	expected = []string{"Stat_2"}
	ev1 = &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "event2",
		Event: map[string]any{
			utils.AccountField: "1002",
		},
		APIOpts: map[string]any{
			utils.MetaUsage:     6 * time.Second,
			utils.MetaStartTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC),
			utils.MetaCost:      17.5,
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.StatSv1ProcessEvent, &ev1, &reply); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(reply, expected) {
		t.Errorf("Expecting: %+v, received: %+v", expected, reply)
	}

	expected = []string{"Stat_3"}
	ev1 = &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "event3",
		Event: map[string]any{
			utils.AccountField: "1003",
		},
		APIOpts: map[string]any{
			utils.MetaUsage:     11 * time.Second,
			utils.MetaStartTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC),
			utils.MetaCost:      12.5,
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.StatSv1ProcessEvent, &ev1, &reply); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(reply, expected) {
		t.Errorf("Expecting: %+v, received: %+v", expected, reply)
	}

	expected = []string{"Stat_1_1"}
	ev1 = &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "event3",
		Event: map[string]any{
			"Stat": "Stat1_1",
		},
		APIOpts: map[string]any{
			utils.MetaPDD:       12 * time.Second,
			utils.MetaUsage:     11 * time.Second,
			utils.MetaStartTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC),
			utils.MetaCost:      12.5,
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.StatSv1ProcessEvent, &ev1, &reply); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(reply, expected) {
		t.Errorf("Expecting: %+v, received: %+v", expected, reply)
	}

	expected = []string{"Stat_1_1"}
	ev1 = &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "event3",
		Event: map[string]any{
			"Stat": "Stat1_1",
		},
		APIOpts: map[string]any{
			utils.MetaPDD:       15 * time.Second,
			utils.MetaUsage:     15 * time.Second,
			utils.MetaStartTime: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC),
			utils.MetaCost:      15.5,
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.StatSv1ProcessEvent, &ev1, &reply); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(reply, expected) {
		t.Errorf("Expecting: %+v, received: %+v", expected, reply)
	}
}

func testV1FltrPopulateThreshold(t *testing.T) {
	//Add a filter of type *stats and check if acd metric is minim 10 ( greater than 10)
	//we expect that acd from Stat_1 to be 11 so the filter should pass (11 > 10)
	filter := &engine.FilterWithAPIOpts{
		Filter: &engine.Filter{
			Tenant: "cgrates.org",
			ID:     "FLTR_TH_Stats1",
			Rules: []*engine.FilterRule{
				{
					Type:    "*gt",
					Element: "~*stats.Stat_1.*acd",
					Values:  []string{"10.0"},
				},
			},
		},
	}

	var result string
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetFilter, filter, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}

	// Add a disable and log action
	argsAP := &utils.ActionProfileWithAPIOpts{
		ActionProfile: &utils.ActionProfile{
			ID: "LOG",
			Actions: []*utils.APAction{
				{
					ID:   "LogAction",
					Type: utils.MetaLog,
				},
			},
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetActionProfile, argsAP, &result); err != nil && err.Error() != utils.ErrExists.Error() {
		t.Error("Got error on APIerSv2.SetActions: ", err.Error())
	} else if result != utils.OK {
		t.Errorf("Calling APIerSv2.SetActions received: %s", result)
	}

	//Add a threshold with filter from above and an inline filter for Account 1010
	tPrfl := &engine.ThresholdProfileWithAPIOpts{
		ThresholdProfile: &engine.ThresholdProfile{
			Tenant:    "cgrates.org",
			ID:        "TH_Stats1",
			FilterIDs: []string{"FLTR_TH_Stats1", "*string:~*req.Account:1010"},
			MaxHits:   -1,
			MinSleep:  time.Millisecond,
			Weights: utils.DynamicWeights{
				{
					Weight: 10,
				},
			},
			ActionProfileIDs: []string{"LOG"},
			Async:            true,
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetThresholdProfile, tPrfl, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	var rcvTh *engine.ThresholdProfile
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1GetThresholdProfile,
		&utils.TenantIDWithAPIOpts{
			TenantID: &utils.TenantID{
				Tenant: tPrfl.Tenant,
				ID:     tPrfl.ID,
			},
		}, &rcvTh); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(tPrfl.ThresholdProfile, rcvTh) {
		t.Errorf("Expecting: %+v, received: %+v", tPrfl.ThresholdProfile, rcvTh)
	}
}

func testV1FltrGetThresholdForEvent(t *testing.T) {
	// check the event
	tEv := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "event1",
		Event: map[string]any{
			utils.AccountField: "1010"},
	}
	var ids []string
	eIDs := []string{"TH_Stats1"}
	if err := fltrRpc.Call(context.Background(), utils.ThresholdSv1ProcessEvent, tEv, &ids); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(ids, eIDs) {
		t.Errorf("Expecting ids: %s, received: %s", eIDs, ids)
	}
}

func testV1FltrGetThresholdForEvent2(t *testing.T) {
	//Add a filter of type *stats and check if acd metric is maximum 10 ( lower than 10)
	//we expect that acd from Stat_1 to be 11 so the filter should not pass (11 > 10)
	filter := &engine.FilterWithAPIOpts{
		Filter: &engine.Filter{
			Tenant: "cgrates.org",
			ID:     "FLTR_TH_Stats1",
			Rules: []*engine.FilterRule{
				{
					Type:    "*lt",
					Element: "~*stats.Stat_1.*acd",
					Values:  []string{"10.0"},
				},
			},
		},
	}

	var result string
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetFilter, filter, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}

	//update the threshold with new filter
	tPrfl := &engine.ThresholdProfileWithAPIOpts{
		ThresholdProfile: &engine.ThresholdProfile{
			Tenant:    "cgrates.org",
			ID:        "TH_Stats1",
			FilterIDs: []string{"FLTR_TH_Stats1", "*string:~*req.Account:1010", "*ai:~*opts.*startTime:2014-07-14T14:35:00Z|2014-07-14T14:36:00Z"},
			MaxHits:   -1,
			MinSleep:  time.Millisecond,
			Weights: utils.DynamicWeights{
				{
					Weight: 10,
				},
			},
			ActionProfileIDs: []string{"LOG"},
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetThresholdProfile, tPrfl, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}

	tEv := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "event1",
		Event: map[string]any{
			utils.AccountField: "1010"},
	}
	var ids []string
	if err := fltrRpc.Call(context.Background(), utils.ThresholdSv1ProcessEvent, tEv, &ids); err == nil ||
		err.Error() != utils.ErrNotFound.Error() {
		t.Error(err)
	}
}

func testV1FltrPopulateResources(t *testing.T) {
	//create a resourceProfile
	rlsConfig := &utils.ResourceProfile{
		Tenant:            "cgrates.org",
		ID:                "ResTest",
		UsageTTL:          time.Minute,
		Limit:             10,
		AllocationMessage: "MessageAllocation",
		Stored:            true,
		Weights: utils.DynamicWeights{
			{
				Weight: 20,
			},
		},
		ThresholdIDs: []string{utils.MetaNone},
	}

	var result string
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetResourceProfile, &utils.ResourceProfileWithAPIOpts{ResourceProfile: rlsConfig}, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}

	var reply *utils.ResourceProfile
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1GetResourceProfile,
		&utils.TenantIDWithAPIOpts{
			TenantID: &utils.TenantID{
				Tenant: "cgrates.org",
				ID:     rlsConfig.ID,
			},
		}, &reply); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(reply, rlsConfig) {
		t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(rlsConfig), utils.ToJSON(reply))
	}

	// Allocate 3 units for resource ResTest
	argsRU := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     utils.UUIDSha1Prefix(),
		Event: map[string]any{
			"Account":     "3001",
			"Destination": "3002",
		},
		APIOpts: map[string]any{
			utils.OptsResourcesUsageID: "651a8db2-4f67-4cf8-b622-169e8a482e21",
			utils.OptsResourcesUnits:   3,
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.ResourceSv1AllocateResources,
		argsRU, &result); err != nil {
		t.Error(err)
	}

	//we allocate 3 units to resource and add a filter for Usages > 2
	//should match (3>2)
	filter := engine.FilterWithAPIOpts{
		Filter: &engine.Filter{
			Tenant: "cgrates.org",
			ID:     "FLTR_TH_Resource",
			Rules: []*engine.FilterRule{
				{
					Type:    "*gt",
					Element: "~*resources.ResTest.TotalUsage",
					Values:  []string{"2.0"},
				},
			},
		},
	}

	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetFilter, filter, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}

	tPrfl := &engine.ThresholdProfileWithAPIOpts{
		ThresholdProfile: &engine.ThresholdProfile{
			Tenant:    "cgrates.org",
			ID:        "TH_ResTest",
			FilterIDs: []string{"FLTR_TH_Resource", "*string:~*req.Account:2020"},
			MaxHits:   -1,
			MinSleep:  time.Millisecond,
			Weights: utils.DynamicWeights{
				{
					Weight: 10,
				},
			},
			ActionProfileIDs: []string{"LOG"},
			Async:            true,
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetThresholdProfile, tPrfl, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	var rcvTh *engine.ThresholdProfile
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1GetThresholdProfile,
		&utils.TenantIDWithAPIOpts{
			TenantID: &utils.TenantID{
				Tenant: tPrfl.Tenant,
				ID:     tPrfl.ID},
		}, &rcvTh); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(tPrfl.ThresholdProfile, rcvTh) {
		t.Errorf("Expecting: %+v, received: %+v", tPrfl.ThresholdProfile, rcvTh)
	}

	// check the event
	tEv := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "event1",
		Event: map[string]any{
			utils.AccountField: "2020"},
	}

	var ids []string
	eIDs := []string{"TH_ResTest"}
	if err := fltrRpc.Call(context.Background(), utils.ThresholdSv1ProcessEvent, tEv, &ids); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(ids, eIDs) {
		t.Errorf("Expecting ids: %s, received: %s", eIDs, ids)
	}

	//change the filter
	//we allocate 3 units to resource and add a filter for Usages < 2
	//should fail (3<2)
	filter.Filter = &engine.Filter{
		Tenant: "cgrates.org",
		ID:     "FLTR_TH_Resource",
		Rules: []*engine.FilterRule{
			{
				Type:    "*lt",
				Element: "~*resources.ResTest.TotalUsage",
				Values:  []string{"2.0"},
			},
		},
	}

	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetFilter, filter, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}

	//Overwrite the threshold
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetThresholdProfile, tPrfl, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}

	//expect NotFound error because filter doesn't match
	if err := fltrRpc.Call(context.Background(), utils.ThresholdSv1ProcessEvent, tEv, &ids); err == nil ||
		err.Error() != utils.ErrNotFound.Error() {
		t.Error(err)
	}
}

func testV1FltrPopulateResourcesAvailableUnits(t *testing.T) {
	//create a resourceProfile
	rlsConfig := &utils.ResourceProfile{
		Tenant:            "cgrates.org",
		ID:                "RES_TEST",
		UsageTTL:          time.Minute,
		Limit:             23,
		AllocationMessage: "Test_Available",
		Stored:            true,
		Weights: utils.DynamicWeights{
			{
				Weight: 25,
			},
		},
		ThresholdIDs: []string{utils.MetaNone},
	}

	var result string
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetResourceProfile, &utils.ResourceProfileWithAPIOpts{ResourceProfile: rlsConfig}, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}

	var reply *utils.ResourceProfile
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1GetResourceProfile,
		&utils.TenantIDWithAPIOpts{
			TenantID: &utils.TenantID{
				Tenant: "cgrates.org",
				ID:     rlsConfig.ID},
		}, &reply); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(reply, rlsConfig) {
		t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(rlsConfig), utils.ToJSON(reply))
	}

	//Allocate 9 units for resource ResTest
	argsRU := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     utils.UUIDSha1Prefix(),
		Event: map[string]any{
			"Account":     "3001",
			"Destination": "3002"},
		APIOpts: map[string]any{
			utils.OptsResourcesUsageID: "651a8db2-4f67-4cf8-b622-169e8a482e21",
			utils.OptsResourcesUnits:   9,
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.ResourceSv1AllocateResources, argsRU, &result); err != nil {
		t.Error(err)
	} else if result != "Test_Available" {
		t.Error("Unexpected reply returned", result)
	}

	//as we allocate 9 units, there should be available 14 more
	//our filter should match for *gt or *gte
	filter := engine.FilterWithAPIOpts{
		Filter: &engine.Filter{
			Tenant: "cgrates.org",
			ID:     "FLTR_ST_Resource1",
			Rules: []*engine.FilterRule{
				{
					Type:    "*gt",
					Element: "~*resources.RES_TEST.Available",
					Values:  []string{"13.0"},
				},
				{
					Type:    "*gte",
					Element: "~*resources.RES_TEST.Available",
					Values:  []string{"14.0"},
				},
			},
		},
	}

	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetFilter, filter, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}

	//set a statQueueProfile with that filter
	statsPrf := &engine.StatQueueProfileWithAPIOpts{
		StatQueueProfile: &engine.StatQueueProfile{
			Tenant:    "cgrates.org",
			ID:        "STATS_RES_TEST12",
			FilterIDs: []string{"FLTR_ST_Resource1", "*string:~*req.Account:1001"},
			Weights: utils.DynamicWeights{
				{
					Weight: 50,
				},
			},
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetStatQueueProfile, statsPrf, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}

	var replyStats *engine.StatQueueProfile
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1GetStatQueueProfile, &utils.TenantIDWithAPIOpts{
		TenantID: &utils.TenantID{
			Tenant: "cgrates.org",
			ID:     "STATS_RES_TEST12"},
	}, &replyStats); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(statsPrf.StatQueueProfile, replyStats) {
		t.Errorf("Expected %+v, received %+v", utils.ToJSON(statsPrf.StatQueueProfile), utils.ToJSON(replyStats))
	}

	//here will check the event
	statsEv := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "event_nr2",
		Event: map[string]any{
			utils.AccountField: "1001",
		},
		APIOpts: map[string]any{
			utils.MetaUsage: "1",
		},
	}
	var ids []string
	expectedIDs := []string{"STATS_RES_TEST12", "Stat_1"}
	if err := fltrRpc.Call(context.Background(), utils.StatSv1ProcessEvent, statsEv, &ids); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(expectedIDs, ids) {
		t.Errorf("Expected %+v, received %+v", expectedIDs, ids)
	}

	//set another filter that will not match
	filter = engine.FilterWithAPIOpts{
		Filter: &engine.Filter{
			Tenant: "cgrates.org",
			ID:     "FLTR_ST_Resource1",
			Rules: []*engine.FilterRule{
				{
					Type:    "*gt",
					Element: "~*resources.RES_TEST.Available",
					Values:  []string{"17.0"},
				},
			},
		},
	}

	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetFilter, filter, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}

	//overwrite the StatQueueProfile
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1GetStatQueueProfile, &utils.TenantIDWithAPIOpts{
		TenantID: &utils.TenantID{
			Tenant: "cgrates.org",
			ID:     "STATS_RES_TEST12"},
	}, &replyStats); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(statsPrf.StatQueueProfile, replyStats) {
		t.Errorf("Expected %+v, received %+v", utils.ToJSON(statsPrf.StatQueueProfile), utils.ToJSON(replyStats))
	}

	//This filter won't match
	expectedIDs = []string{"Stat_1"}
	if err := fltrRpc.Call(context.Background(), utils.StatSv1ProcessEvent, statsEv, &ids); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(expectedIDs, ids) {
		t.Errorf("Expected %+v, received %+v", expectedIDs, ids)
	}
}

func testV1FltrAccounts(t *testing.T) {
	var resp string
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1RemoveThresholdProfile,
		&utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "THD_ACNT_1001"}}, &resp); err != nil {
		t.Error(err)
	} else if resp != utils.OK {
		t.Error("Unexpected reply returned", resp)
	}
	// Add a filter with fieldName taken value from account 1001
	// and check if *monetary balance is minim 9 ( greater than 9)
	// we expect that the balance to be 10 so the filter should pass (10 > 9)
	filter := engine.FilterWithAPIOpts{
		Filter: &engine.Filter{
			Tenant: "cgrates.org",
			ID:     "FLTR_TH_Accounts",
			Rules: []*engine.FilterRule{
				{
					Type:    "*gt",
					Element: "~*accounts.1001.Balances.VoiceBalance.Units",
					// Element: "~*accounts.1001.BalanceMap.*monetary[0].Value",
					Values: []string{"1m"},
				},
			},
		},
	}

	var result string
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetFilter, filter, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	// Add a log action
	argsAP := &utils.ActionProfileWithAPIOpts{
		ActionProfile: &utils.ActionProfile{
			ID: "LOG",
			Actions: []*utils.APAction{
				{
					ID:   "LogAction",
					Type: utils.MetaLog,
				},
			},
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetActionProfile, argsAP, &result); err != nil && err.Error() != utils.ErrExists.Error() {
		t.Error("Got error on APIerSv2.SetActions: ", err.Error())
	} else if result != utils.OK {
		t.Errorf("Calling APIerSv2.SetActions received: %s", result)
	}
	//Add a threshold with filter from above and an inline filter for Account 1010
	tPrfl := &engine.ThresholdProfileWithAPIOpts{
		ThresholdProfile: &engine.ThresholdProfile{
			Tenant:    "cgrates.org",
			ID:        "TH_Account",
			FilterIDs: []string{"FLTR_TH_Accounts", "*string:~*req.Account:1001"},
			MaxHits:   -1,
			MinSleep:  time.Millisecond,
			Weights: utils.DynamicWeights{
				{
					Weight: 90,
				},
			},
			ActionProfileIDs: []string{"LOG"},
			Async:            true,
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetThresholdProfile, tPrfl, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	var rcvTh *engine.ThresholdProfile
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1GetThresholdProfile,
		&utils.TenantIDWithAPIOpts{
			TenantID: &utils.TenantID{
				Tenant: tPrfl.Tenant,
				ID:     tPrfl.ID},
		}, &rcvTh); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(tPrfl.ThresholdProfile, rcvTh) {
		t.Errorf("Expecting: %+v, received: %+v", tPrfl.ThresholdProfile, rcvTh)
	}

	tEv := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "event1",
		Event: map[string]any{
			utils.AccountField: "1001"},
	}
	var ids []string
	if err := fltrRpc.Call(context.Background(), utils.ThresholdSv1ProcessEvent, tEv, &ids); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(ids, []string{"TH_Account"}) {
		t.Error("Unexpected reply returned", ids)
	}

	// update the filter
	// Add a filter with fieldName taken value from account 1001
	// and check if *monetary balance is is minim 11 ( greater than 11)
	// we expect that the balance to be 10 so the filter should not pass (10 > 11)
	filter.Filter = &engine.Filter{
		Tenant: "cgrates.org",
		ID:     "FLTR_TH_Accounts",
		Rules: []*engine.FilterRule{
			{
				Type:    "*gt",
				Element: "~*accounts.1001.BalanceMap.*monetary[0].Value",
				Values:  []string{"11"},
			},
		},
	}

	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetFilter, filter, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}

	if err := fltrRpc.Call(context.Background(), utils.ThresholdSv1ProcessEvent, tEv, &ids); err == nil ||
		err.Error() != utils.ErrNotFound.Error() {
		t.Error(err)
	}
}

func testV1FltrAccountsExistsDynamicaly(t *testing.T) {
	var resp string
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1RemoveThresholdProfile,
		&utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "TH_Account"}}, &resp); err != nil {
		if err.Error() != utils.ErrNotFound.Error() { // no error if the threshold is already removed
			t.Error(err)
		}
	} else if resp != utils.OK {
		t.Error("Unexpected reply returned", resp)
	}

	var result string
	// Add a log action
	argsAP := &utils.ActionProfileWithAPIOpts{
		ActionProfile: &utils.ActionProfile{
			ID: "LOG",
			Actions: []*utils.APAction{
				{
					ID:   "LogAction",
					Type: utils.MetaLog,
				},
			},
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetActionProfile, argsAP, &result); err != nil && err.Error() != utils.ErrExists.Error() {
		t.Error("Got error on AdminSv1.SetActionProfile: ", err.Error())
	}
	//Add a threshold with filter from above and an inline filter for Account 1010
	tPrfl := &engine.ThresholdProfileWithAPIOpts{
		ThresholdProfile: &engine.ThresholdProfile{
			Tenant:    "cgrates.org",
			ID:        "TH_AccountDinamic",
			FilterIDs: []string{"*exists:~*accounts.<~*req.Account>:"},
			MaxHits:   -1,
			MinSleep:  time.Millisecond,
			Weights: utils.DynamicWeights{
				{
					Weight: 90,
				},
			},
			ActionProfileIDs: []string{"LOG"},
			Async:            true,
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetThresholdProfile, tPrfl, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	var rcvTh *engine.ThresholdProfile
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1GetThresholdProfile,
		&utils.TenantIDWithAPIOpts{
			TenantID: &utils.TenantID{
				Tenant: tPrfl.Tenant,
				ID:     tPrfl.ID,
			},
		}, &rcvTh); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(tPrfl.ThresholdProfile, rcvTh) {
		t.Errorf("Expecting: %+v, received: %+v", tPrfl.ThresholdProfile, rcvTh)
	}

	tEv := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "event1",
		Event: map[string]any{
			utils.AccountField: "1001"},
	}
	var ids []string
	if err := fltrRpc.Call(context.Background(), utils.ThresholdSv1ProcessEvent, tEv, &ids); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(ids, []string{"TH_AccountDinamic"}) {
		t.Error("Unexpected reply returned", ids)
	}

	tEv = &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "event2",
		Event: map[string]any{
			utils.AccountField: "non"},
	}
	ids = nil
	if err := fltrRpc.Call(context.Background(), utils.ThresholdSv1ProcessEvent, tEv, &ids); err == nil || err.Error() != utils.ErrNotFound.Error() {
		t.Error(err)
	}
}

func testV1FltrChargerSuffix(t *testing.T) {
	var reply string
	if err := fltrRpc.Call(context.Background(), utils.CacheSv1Clear, &utils.AttrCacheIDsWithAPIOpts{
		CacheIDs: nil,
	}, &reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Error("Reply: ", reply)
	}
	chargerProfile := &utils.ChargerProfileWithAPIOpts{
		ChargerProfile: &utils.ChargerProfile{
			Tenant:       "cgrates.org",
			ID:           "IntraCharger",
			FilterIDs:    []string{"*suffix:~*req.Subject:intra"},
			RunID:        "Intra",
			AttributeIDs: []string{"*constant:*req.Subject:intraState"},
			Weights: utils.DynamicWeights{
				{
					Weight: 100,
				},
			},
			Blockers: utils.DynamicBlockers{
				{
					Blocker: true,
				},
			},
		},
	}
	var result string
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetChargerProfile, chargerProfile, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}

	chargerProfile2 := &utils.ChargerProfileWithAPIOpts{
		ChargerProfile: &utils.ChargerProfile{
			Tenant:       "cgrates.org",
			ID:           "InterCharger",
			FilterIDs:    []string{"*suffix:~*req.Subject:inter"},
			RunID:        "Inter",
			AttributeIDs: []string{"*constant:*req.Subject:interState"},
			Weights: utils.DynamicWeights{
				{
					Weight: 100,
				},
			},
			Blockers: utils.DynamicBlockers{
				{
					Blocker: true,
				},
			},
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetChargerProfile, chargerProfile2, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}

	processedEv := []*chargers.ChrgSProcessEventReply{
		{
			ChargerSProfile: "IntraCharger",
			AlteredFields: []*attributes.FieldsAltered{
				{
					MatchedProfileID: utils.MetaDefault,
					Fields:           []string{utils.MetaOptsRunID, utils.MetaOpts + utils.NestingSep + utils.MetaChargeID, utils.MetaOpts + utils.NestingSep + utils.MetaSubsys},
				},
				{
					MatchedProfileID: "*constant:*req.Subject:intraState",
					Fields:           []string{"*req.Subject"},
				},
			},
			CGREvent: &utils.CGREvent{ // matching Charger1
				Tenant: "cgrates.org",
				ID:     "event1",
				Event: map[string]any{
					utils.AccountField: "1010",
					utils.Subject:      "intraState",
					utils.Destination:  "999",
				},
				APIOpts: map[string]any{
					utils.MetaRunID:                "Intra",
					utils.OptsAttributesProfileIDs: []string{"*constant:*req.Subject:intraState"},
					utils.MetaSubsys:               utils.MetaChargers,
					utils.OptsContext:              "*chargers",
				},
			},
		},
	}
	cgrEv := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "event1",
		Event: map[string]any{
			utils.AccountField: "1010",
			utils.Subject:      "Something_intra",
			utils.Destination:  "999",
		},
	}

	var result2 []*chargers.ChrgSProcessEventReply
	if err := fltrRpc.Call(context.Background(), utils.ChargerSv1ProcessEvent, cgrEv, &result2); err != nil {
		t.Error(err)
	} else {
		sort.Slice(result2, func(i, j int) bool {
			return result2[i].ChargerSProfile < result2[j].ChargerSProfile
		})
		if !reflect.DeepEqual(result2[0].AlteredFields, processedEv[0].AlteredFields) {
			t.Errorf("Expecting : %s, \n received: %s", utils.ToJSON(processedEv[0]), utils.ToJSON(result2))
		}
	}

	processedEv = []*chargers.ChrgSProcessEventReply{
		{
			ChargerSProfile: "InterCharger",
			AlteredFields: []*attributes.FieldsAltered{
				{
					MatchedProfileID: utils.MetaDefault,
					Fields:           []string{utils.MetaOptsRunID, utils.MetaOpts + utils.NestingSep + utils.MetaChargeID, utils.MetaOpts + utils.NestingSep + utils.MetaSubsys},
				},
				{
					MatchedProfileID: "*constant:*req.Subject:interState",
					Fields:           []string{"*req.Subject"},
				},
			},
			CGREvent: &utils.CGREvent{ // matching Charger1
				Tenant: "cgrates.org",
				ID:     "event1",
				Event: map[string]any{
					utils.AccountField: "1010",
					utils.Subject:      "interState",
					utils.Destination:  "999",
				},
				APIOpts: map[string]any{
					utils.MetaSubsys:               utils.MetaChargers,
					utils.MetaRunID:                "Inter",
					utils.OptsContext:              "*chargers",
					utils.OptsAttributesProfileIDs: []string{"*constant:*req.Subject:interState"},
				},
			},
		},
	}
	cgrEv = &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "event1",
		Event: map[string]any{
			utils.AccountField: "1010",
			utils.Subject:      "Something_inter",
			utils.Destination:  "999",
		},
	}
	if err := fltrRpc.Call(context.Background(), utils.ChargerSv1ProcessEvent, cgrEv, &result2); err != nil {
		t.Error(err)
	} else {
		sort.Slice(result2, func(i, j int) bool {
			return result2[i].ChargerSProfile < result2[j].ChargerSProfile
		})
		if !reflect.DeepEqual(result2[0].AlteredFields, processedEv[0].AlteredFields) {
			t.Errorf("Expecting : %s, \n received: %s", utils.ToJSON(processedEv[0]), utils.ToJSON(result2[0]))
		}
	}
}

func testV1FltrAttributesPrefix(t *testing.T) {
	attributeProfile := &utils.APIAttributeProfileWithAPIOpts{
		APIAttributeProfile: &utils.APIAttributeProfile{
			Tenant:    "cgrates.new",
			ID:        "ATTR_1001",
			FilterIDs: []string{"*prefix:~*req.CustomField:2007|+2007", "*prefix:~*req.CustomField2:2007|+2007", "FLTR_1", "*string:~*opts.*context:prefix"},
			Attributes: []*utils.ExternalAttribute{
				{
					FilterIDs: []string{},
					Path:      utils.MetaReq + utils.NestingSep + "CustomField",
					Type:      utils.MetaConstant,
					Value:     "2007",
				},
			},
			Weights: utils.DynamicWeights{
				{
					Weight: 20,
				},
			},
		},
	}
	var result string
	if err := fltrRpc.Call(context.Background(), utils.AdminSv1SetAttributeProfile, attributeProfile, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}

	processedEv := &attributes.AttrSProcessEventReply{
		AlteredFields: []*attributes.FieldsAltered{
			{
				MatchedProfileID: "cgrates.new:ATTR_1001",
				Fields:           []string{"*req.CustomField"},
			},
		},
		CGREvent: &utils.CGREvent{
			Tenant: "cgrates.new",
			ID:     "event1",
			Event: map[string]any{
				"CustomField":     "2007",
				"CustomField2":    "+2007",
				utils.Destination: "+1207",
			},
			APIOpts: map[string]any{
				utils.OptsContext: "prefix",
			},
		},
	}
	cgrEv := &utils.CGREvent{
		Tenant: "cgrates.new",
		ID:     "event1",
		Event: map[string]any{
			"CustomField":     "+2007",
			"CustomField2":    "+2007",
			utils.Destination: "+1207",
		},
		APIOpts: map[string]any{
			utils.OptsContext: "prefix",
		},
	}
	var result2 *attributes.AttrSProcessEventReply
	if err := fltrRpc.Call(context.Background(), utils.AttributeSv1ProcessEvent, cgrEv, &result2); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(result2, processedEv) {
		t.Errorf("Expecting : %s, \n received: %s", utils.ToJSON(processedEv), utils.ToJSON(result2))
	}

}

func testV1FltrStopEngine(t *testing.T) {
	if err := engine.KillEngine(accDelay); err != nil {
		t.Error(err)
	}
}
