//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"
	"strings"
	"testing"
	"time"

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

var (
	attrCfgPath     string
	attrCfg         *config.CGRConfig
	attrRPC         *birpc.Client
	alsPrfConfigDIR string
	sTestsAlsPrf    = []func(t *testing.T){
		testAttributeSInitCfg,
		testAttributeSInitDataDb,
		testAttributeSResetStorDb,
		testAttributeSStartEngine,
		testAttributeSRPCConn,
		testAttributeSLoadFromFolder,
		testAttributeSProcessEvent,
		testAttributeSProcessEventWithAccount,
		testAttributeSProcessEventWithAccountFull,
		testAttributeSProcessEventWithStat,
		testAttributeSProcessEventWithStatFull,
		testAttributeSProcessEventWithResource,
		testAttributeSProcessEventWithResourceFull,
		testAttributeSProcessEventWithLibPhoneNumber,
		testAttributeSProcessEventWithLibPhoneNumberComposed,
		testAttributeSProcessEventWithLibPhoneNumberFull,
		testAttributeSStopEngine,
	}
)

func TestAttributeSIT(t *testing.T) {
	switch *utils.DBType {
	case utils.MetaInternal:
		alsPrfConfigDIR = "tutinternal"
	case utils.MetaMySQL:
		alsPrfConfigDIR = "tutmysql"
	case utils.MetaMongo:
		alsPrfConfigDIR = "tutmongo"
	case utils.MetaPostgres:
		t.SkipNow()
	default:
		t.Fatal("Unknown Database type")
	}
	for _, stest := range sTestsAlsPrf {
		t.Run(alsPrfConfigDIR, stest)
	}
}

func testAttributeSInitCfg(t *testing.T) {
	var err error
	attrCfgPath = path.Join(*utils.DataDir, "conf", "samples", alsPrfConfigDIR)
	attrCfg, err = config.NewCGRConfigFromPath(attrCfgPath)
	if err != nil {
		t.Error(err)
	}
	attrCfg.DataFolderPath = *utils.DataDir // Share DataFolderPath through config towards StoreDb for Flush()
}

func testAttributeSInitDataDb(t *testing.T) {
	if err := engine.InitDataDB(attrCfg); err != nil {
		t.Fatal(err)
	}
}

// Wipe out the cdr database
func testAttributeSResetStorDb(t *testing.T) {
	if err := engine.InitStorDb(attrCfg); err != nil {
		t.Fatal(err)
	}
}

// Start CGR Engine
func testAttributeSStartEngine(t *testing.T) {
	if _, err := engine.StopStartEngine(attrCfgPath, *utils.WaitRater); err != nil {
		t.Fatal(err)
	}
}

// Connect rpc client to rater
func testAttributeSRPCConn(t *testing.T) {
	attrRPC = engine.NewRPCClient(t, attrCfg.ListenCfg())
}

func testAttributeSLoadFromFolder(t *testing.T) {
	var reply string
	attrs := &utils.AttrLoadTpFromFolder{FolderPath: path.Join(*utils.DataDir, "tariffplans", "testit")}
	if err := attrRPC.Call(context.Background(), utils.APIerSv1LoadTariffPlanFromFolder, attrs, &reply); err != nil {
		t.Error(err)
	}
	time.Sleep(200 * time.Millisecond)
}

func testAttributeSProcessEvent(t *testing.T) {
	ev := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "testAttributeSProcessEvent",
		Event: map[string]any{
			utils.EventName: "VariableTest",
			utils.ToR:       utils.MetaVoice,
		},
		APIOpts: map[string]any{
			utils.OptsContext: utils.MetaSessionS,
		},
	}
	eRply := engine.AttrSProcessEventReply{
		MatchedProfiles: []string{"cgrates.org:ATTR_VARIABLE"},
		AlteredFields:   []string{utils.MetaReq + utils.NestingSep + utils.Category},
		CGREvent: &utils.CGREvent{
			Tenant: "cgrates.org",
			ID:     "testAttributeSProcessEvent",
			Event: map[string]any{
				utils.EventName: "VariableTest",
				utils.Category:  utils.MetaVoice,
				utils.ToR:       utils.MetaVoice,
			},
			APIOpts: map[string]any{
				utils.OptsContext: utils.MetaSessionS,
			},
		},
	}
	sort.Strings(eRply.AlteredFields)
	var rplyEv engine.AttrSProcessEventReply
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
		ev, &rplyEv); err != nil {
		t.Fatal(err)
	}
	sort.Strings(rplyEv.AlteredFields)
	if !reflect.DeepEqual(eRply, rplyEv) {
		t.Errorf("Expecting: %s, received: %s",
			utils.ToJSON(eRply), utils.ToJSON(rplyEv))
	}
}

func testAttributeSProcessEventWithAccount(t *testing.T) {
	// add new attribute profile
	var result string
	alsPrf := &engine.AttributeProfileWithAPIOpts{
		AttributeProfile: &engine.AttributeProfile{
			Tenant:    "cgrates.org",
			ID:        "ATTR_ACCOUNT",
			Contexts:  []string{utils.MetaAny},
			FilterIDs: []string{"*string:~*req.EventName:AddAccountInfo"},
			ActivationInterval: &utils.ActivationInterval{
				ActivationTime: time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC),
				ExpiryTime:     time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC),
			},
			Attributes: []*engine.Attribute{
				{
					Path: utils.MetaReq + utils.NestingSep + "Balance",
					Type: utils.MetaVariable,
					Value: config.RSRParsers{
						&config.RSRParser{
							Rules: "~*accounts.1001.BalanceMap.*monetary[0].Value",
						},
					},
				},
			},
			Blocker: false,
			Weight:  10,
		},
	}
	alsPrf.Compile()
	if err := attrRPC.Call(context.Background(), utils.APIerSv1SetAttributeProfile, alsPrf, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	var replyAttr *engine.AttributeProfile
	if err := attrRPC.Call(context.Background(), utils.APIerSv1GetAttributeProfile,
		utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "ATTR_ACCOUNT"}}, &replyAttr); err != nil {
		t.Fatal(err)
	}
	replyAttr.Compile()
	if !reflect.DeepEqual(alsPrf.AttributeProfile, replyAttr) {
		t.Errorf("Expecting : %+v, received: %+v", alsPrf.AttributeProfile, replyAttr)
	}

	ev := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "testAttributeSProcessEventWithAccount",
		Event: map[string]any{
			"EventName": "AddAccountInfo",
		},
		APIOpts: map[string]any{
			utils.OptsContext: utils.MetaSessionS,
		},
	}

	eRply := engine.AttrSProcessEventReply{
		MatchedProfiles: []string{"cgrates.org:ATTR_ACCOUNT"},
		AlteredFields:   []string{utils.MetaReq + utils.NestingSep + "Balance"},
		CGREvent: &utils.CGREvent{
			Tenant: "cgrates.org",
			ID:     "testAttributeSProcessEventWithAccount",
			Event: map[string]any{
				"EventName": "AddAccountInfo",
				"Balance":   "10",
			},
			APIOpts: map[string]any{
				utils.OptsContext: utils.MetaSessionS,
			},
		},
	}
	sort.Strings(eRply.AlteredFields)
	var rplyEv engine.AttrSProcessEventReply
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
		ev, &rplyEv); err != nil {
		t.Fatal(err)
	}
	sort.Strings(rplyEv.AlteredFields)
	if !reflect.DeepEqual(eRply, rplyEv) {
		t.Errorf("Expecting: %s, received: %s",
			utils.ToJSON(eRply), utils.ToJSON(rplyEv))
	}
}

func testAttributeSProcessEventWithAccountFull(t *testing.T) {
	// add new attribute profile
	var result string
	alsPrf := &engine.AttributeProfileWithAPIOpts{
		AttributeProfile: &engine.AttributeProfile{
			Tenant:    "cgrates.org",
			ID:        "ATTR_ACCOUNT2",
			Contexts:  []string{utils.MetaAny},
			FilterIDs: []string{"*string:~*req.EventName:AddFullAccount"},
			ActivationInterval: &utils.ActivationInterval{
				ActivationTime: time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC),
				ExpiryTime:     time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC),
			},
			Attributes: []*engine.Attribute{
				{
					Path: utils.MetaReq + utils.NestingSep + "FullAccount",
					Type: utils.MetaVariable,
					Value: config.RSRParsers{
						&config.RSRParser{
							Rules: "~*accounts.1001",
						},
					},
				},
			},
			Blocker: false,
			Weight:  10,
		},
	}
	alsPrf.Compile()
	if err := attrRPC.Call(context.Background(), utils.APIerSv1SetAttributeProfile, alsPrf, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	var replyAttr *engine.AttributeProfile
	if err := attrRPC.Call(context.Background(), utils.APIerSv1GetAttributeProfile,
		utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "ATTR_ACCOUNT2"}}, &replyAttr); err != nil {
		t.Fatal(err)
	}
	replyAttr.Compile()
	if !reflect.DeepEqual(alsPrf.AttributeProfile, replyAttr) {
		t.Errorf("Expecting : %+v, received: %+v", alsPrf.AttributeProfile, replyAttr)
	}

	ev := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "testAttributeSProcessEventWithAccount2",
		Event: map[string]any{
			"EventName": "AddFullAccount",
		},
		APIOpts: map[string]any{
			utils.OptsContext: utils.MetaSessionS,
		},
	}

	eRply := engine.AttrSProcessEventReply{
		MatchedProfiles: []string{"cgrates.org:ATTR_ACCOUNT2"},
		AlteredFields:   []string{utils.MetaReq + utils.NestingSep + "FullAccount"},
		CGREvent: &utils.CGREvent{
			Tenant: "cgrates.org",
			ID:     "testAttributeSProcessEventWithAccount2",
			Event: map[string]any{
				"EventName":   "AddFullAccount",
				"FullAccount": "{\"ID\":\"cgrates.org:1001\",\"BalanceMap\":{\"*monetary\":[{\"Uuid\":\"18160631-a4ae-4078-8048-b4c6b87a36c6\",\"ID\":\"\",\"Value\":10,\"ExpirationDate\":\"0001-01-01T00:00:00Z\",\"Weight\":10,\"DestinationIDs\":{},\"RatingSubject\":\"\",\"Categories\":{},\"SharedGroups\":{},\"Timings\":null,\"TimingIDs\":{},\"Disabled\":false,\"Factor\":null,\"Blocker\":false}]},\"UnitCounters\":null,\"ActionTriggers\":null,\"AllowNegative\":false,\"Disabled\":false,\"UpdateTime\":\"2020-10-06T12:43:51.805Z\"}",
			},
			APIOpts: map[string]any{},
		},
	}
	sort.Strings(eRply.AlteredFields)
	var rplyEv engine.AttrSProcessEventReply
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
		ev, &rplyEv); err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(rplyEv.MatchedProfiles, eRply.MatchedProfiles) {
		t.Errorf("Expecting: %s, received: %s",
			utils.ToJSON(eRply.MatchedProfiles), utils.ToJSON(rplyEv.MatchedProfiles))
	} else if !reflect.DeepEqual(rplyEv.AlteredFields, eRply.AlteredFields) {
		t.Errorf("Expecting: %s, received: %s",
			utils.ToJSON(eRply.AlteredFields), utils.ToJSON(rplyEv.AlteredFields))
	}
	// some fields are generated(e.g. BalanceID) and compare only some part of the string
	strAcc := utils.IfaceAsString(rplyEv.CGREvent.Event["FullAccount"])
	if !strings.Contains(strAcc, "\"ID\":\"cgrates.org:1001\"") {
		t.Errorf("Expecting: %s, received: %s",
			"\"ID\":\"cgrates.org:1001\"", strAcc)
	} else if !strings.Contains(strAcc, "\"UnitCounters\":null,\"ActionTriggers\":null,\"AllowNegative\":false,\"Disabled\":false") {
		t.Errorf("Expecting: %s, received: %s",
			"\"UnitCounters\":null,\"ActionTriggers\":null,\"AllowNegative\":false,\"Disabled\":false", strAcc)
	}
}

func testAttributeSProcessEventWithStat(t *testing.T) {
	// simulate some stat event
	var reply []string
	expected := []string{"Stat_1"}
	ev1 := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "event1",
		Event: map[string]any{
			utils.AccountField: "1001",
			utils.AnswerTime:   time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC),
			utils.Usage:        11 * time.Second,
			utils.Cost:         10.0,
		},
	}
	if err := attrRPC.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",
			utils.AnswerTime:   time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC),
			utils.Usage:        11 * time.Second,
			utils.Cost:         10.5,
		},
	}
	if err := attrRPC.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)
	}

	// add new attribute profile
	alsPrf := &engine.AttributeProfileWithAPIOpts{
		AttributeProfile: &engine.AttributeProfile{
			Tenant:    "cgrates.org",
			ID:        "ATTR_STATS",
			Contexts:  []string{utils.MetaAny},
			FilterIDs: []string{"*string:~*req.EventName:AddStatEvent"},
			ActivationInterval: &utils.ActivationInterval{
				ActivationTime: time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC),
				ExpiryTime:     time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC),
			},
			Attributes: []*engine.Attribute{
				{
					Path: utils.MetaReq + utils.NestingSep + "AcdMetric",
					Type: utils.MetaVariable,
					Value: config.RSRParsers{
						&config.RSRParser{
							Rules: "~*stats.Stat_1.*acd",
						},
					},
				},
			},
			Blocker: false,
			Weight:  10,
		},
	}
	alsPrf.Compile()
	var result string
	if err := attrRPC.Call(context.Background(), utils.APIerSv1SetAttributeProfile, alsPrf, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	var replyAttr *engine.AttributeProfile
	if err := attrRPC.Call(context.Background(), utils.APIerSv1GetAttributeProfile,
		utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "ATTR_STATS"}}, &replyAttr); err != nil {
		t.Fatal(err)
	}
	replyAttr.Compile()
	if !reflect.DeepEqual(alsPrf.AttributeProfile, replyAttr) {
		t.Errorf("Expecting : %+v, received: %+v", alsPrf.AttributeProfile, replyAttr)
	}

	ev := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "testAttributeSProcessEventWithStat",
		Event: map[string]any{
			"EventName": "AddStatEvent",
		},
		APIOpts: map[string]any{
			utils.OptsContext: utils.MetaSessionS,
		},
	}

	eRply := engine.AttrSProcessEventReply{
		MatchedProfiles: []string{"cgrates.org:ATTR_STATS"},
		AlteredFields:   []string{utils.MetaReq + utils.NestingSep + "AcdMetric"},
		CGREvent: &utils.CGREvent{
			Tenant: "cgrates.org",
			ID:     "testAttributeSProcessEventWithStat",
			Event: map[string]any{
				"EventName": "AddStatEvent",
				"AcdMetric": "11000000000",
			},
			APIOpts: map[string]any{
				utils.OptsContext: utils.MetaSessionS,
			},
		},
	}
	sort.Strings(eRply.AlteredFields)
	var rplyEv engine.AttrSProcessEventReply
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
		ev, &rplyEv); err != nil {
		t.Fatal(err)
	}
	sort.Strings(rplyEv.AlteredFields)
	if !reflect.DeepEqual(eRply, rplyEv) {
		t.Errorf("Expecting: %s, received: %s",
			utils.ToJSON(eRply), utils.ToJSON(rplyEv))
	}
}

func testAttributeSProcessEventWithStatFull(t *testing.T) {
	// add new attribute profile
	alsPrf := &engine.AttributeProfileWithAPIOpts{
		AttributeProfile: &engine.AttributeProfile{
			Tenant:    "cgrates.org",
			ID:        "ATTR_STATS2",
			Contexts:  []string{utils.MetaAny},
			FilterIDs: []string{"*string:~*req.EventName:AddFullStats"},
			ActivationInterval: &utils.ActivationInterval{
				ActivationTime: time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC),
				ExpiryTime:     time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC),
			},
			Attributes: []*engine.Attribute{
				{
					Path: utils.MetaReq + utils.NestingSep + "AllMetrics",
					Type: utils.MetaVariable,
					Value: config.RSRParsers{
						&config.RSRParser{
							Rules: "~*stats.Stat_1",
						},
					},
				},
			},
			Blocker: false,
			Weight:  10,
		},
	}
	alsPrf.Compile()
	var result string
	if err := attrRPC.Call(context.Background(), utils.APIerSv1SetAttributeProfile, alsPrf, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	var replyAttr *engine.AttributeProfile
	if err := attrRPC.Call(context.Background(), utils.APIerSv1GetAttributeProfile,
		utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "ATTR_STATS2"}}, &replyAttr); err != nil {
		t.Fatal(err)
	}
	replyAttr.Compile()
	if !reflect.DeepEqual(alsPrf.AttributeProfile, replyAttr) {
		t.Errorf("Expecting : %+v, received: %+v", alsPrf.AttributeProfile, replyAttr)
	}

	ev := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "testAttributeSProcessEventWithStat",
		Event: map[string]any{
			"EventName": "AddFullStats",
		},
		APIOpts: map[string]any{
			utils.OptsContext: utils.MetaSessionS,
		},
	}

	eRply := engine.AttrSProcessEventReply{
		MatchedProfiles: []string{"cgrates.org:ATTR_STATS2"},
		AlteredFields:   []string{utils.MetaReq + utils.NestingSep + "AllMetrics"},
		CGREvent: &utils.CGREvent{
			Tenant: "cgrates.org",
			ID:     "testAttributeSProcessEventWithStat",
			Event: map[string]any{
				"EventName":  "AddFullStats",
				"AllMetrics": "{\"*acd\":11000000000,\"*asr\":100,\"*tcd\":22000000000}",
			},
			APIOpts: map[string]any{
				utils.OptsContext: utils.MetaSessionS,
			},
		},
	}
	sort.Strings(eRply.AlteredFields)
	var rplyEv engine.AttrSProcessEventReply
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
		ev, &rplyEv); err != nil {
		t.Fatal(err)
	}
	sort.Strings(rplyEv.AlteredFields)
	if !reflect.DeepEqual(eRply, rplyEv) {
		t.Errorf("Expecting: %s, received: %s",
			utils.ToJSON(eRply), utils.ToJSON(rplyEv))
	}
}

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

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

	var reply *engine.ResourceProfile
	if err := attrRPC.Call(context.Background(), utils.APIerSv1GetResourceProfile,
		&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 := attrRPC.Call(context.Background(), utils.ResourceSv1AllocateResources,
		argsRU, &result); err != nil {
		t.Error(err)
	}
	// Allocate 2 units for resource ResTest
	argsRU2 := &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-169e8a482e22",
			utils.OptsResourcesUnits:   2,
		},
	}
	if err := attrRPC.Call(context.Background(), utils.ResourceSv1AllocateResources,
		argsRU2, &result); err != nil {
		t.Error(err)
	}

	// add new attribute profile
	alsPrf := &engine.AttributeProfileWithAPIOpts{
		AttributeProfile: &engine.AttributeProfile{
			Tenant:    "cgrates.org",
			ID:        "ATTR_RESOURCE",
			Contexts:  []string{utils.MetaAny},
			FilterIDs: []string{"*string:~*req.EventName:AddResourceUsages"},
			ActivationInterval: &utils.ActivationInterval{
				ActivationTime: time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC),
				ExpiryTime:     time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC),
			},
			Attributes: []*engine.Attribute{
				{
					Path: utils.MetaReq + utils.NestingSep + "ResourceTotalUsages",
					Type: utils.MetaVariable,
					Value: config.RSRParsers{
						&config.RSRParser{
							Rules: "~*resources.ResTest.TotalUsage",
						},
					},
				},
			},
			Blocker: false,
			Weight:  10,
		},
	}
	alsPrf.Compile()
	if err := attrRPC.Call(context.Background(), utils.APIerSv1SetAttributeProfile, alsPrf, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	var replyAttr *engine.AttributeProfile
	if err := attrRPC.Call(context.Background(), utils.APIerSv1GetAttributeProfile,
		utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "ATTR_RESOURCE"}}, &replyAttr); err != nil {
		t.Fatal(err)
	}
	replyAttr.Compile()
	if !reflect.DeepEqual(alsPrf.AttributeProfile, replyAttr) {
		t.Errorf("Expecting : %+v, received: %+v", alsPrf.AttributeProfile, replyAttr)
	}

	ev := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "testAttributeSProcessEventWithResource",
		Event: map[string]any{
			"EventName": "AddResourceUsages",
		},
		APIOpts: map[string]any{
			utils.OptsContext: utils.MetaSessionS,
		},
	}

	eRply := engine.AttrSProcessEventReply{
		MatchedProfiles: []string{"cgrates.org:ATTR_RESOURCE"},
		AlteredFields:   []string{utils.MetaReq + utils.NestingSep + "ResourceTotalUsages"},
		CGREvent: &utils.CGREvent{
			Tenant: "cgrates.org",
			ID:     "testAttributeSProcessEventWithResource",
			Event: map[string]any{
				"EventName":           "AddResourceUsages",
				"ResourceTotalUsages": "5",
			},
			APIOpts: map[string]any{
				utils.OptsContext: utils.MetaSessionS,
			},
		},
	}
	sort.Strings(eRply.AlteredFields)
	var rplyEv engine.AttrSProcessEventReply
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
		ev, &rplyEv); err != nil {
		t.Fatal(err)
	}
	sort.Strings(rplyEv.AlteredFields)
	if !reflect.DeepEqual(eRply, rplyEv) {
		t.Errorf("Expecting: %s, received: %s",
			utils.ToJSON(eRply), utils.ToJSON(rplyEv))
	}
}

func testAttributeSProcessEventWithResourceFull(t *testing.T) {
	// add new attribute profile
	var result string
	alsPrf := &engine.AttributeProfileWithAPIOpts{
		AttributeProfile: &engine.AttributeProfile{
			Tenant:    "cgrates.org",
			ID:        "ATTR_RESOURCE2",
			Contexts:  []string{utils.MetaAny},
			FilterIDs: []string{"*string:~*req.EventName:AddFullResource"},
			ActivationInterval: &utils.ActivationInterval{
				ActivationTime: time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC),
				ExpiryTime:     time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC),
			},
			Attributes: []*engine.Attribute{
				{
					Path: utils.MetaReq + utils.NestingSep + "FullResource",
					Type: utils.MetaVariable,
					Value: config.RSRParsers{
						&config.RSRParser{
							Rules: "~*resources.ResTest",
						},
					},
				},
			},
			Blocker: false,
			Weight:  10,
		},
	}
	alsPrf.Compile()
	if err := attrRPC.Call(context.Background(), utils.APIerSv1SetAttributeProfile, alsPrf, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	var replyAttr *engine.AttributeProfile
	if err := attrRPC.Call(context.Background(), utils.APIerSv1GetAttributeProfile,
		utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "ATTR_RESOURCE2"}}, &replyAttr); err != nil {
		t.Fatal(err)
	}
	replyAttr.Compile()
	if !reflect.DeepEqual(alsPrf.AttributeProfile, replyAttr) {
		t.Errorf("Expecting : %+v, received: %+v", alsPrf.AttributeProfile, replyAttr)
	}

	ev := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "testAttributeSProcessEventWithResource2",
		Event: map[string]any{
			"EventName": "AddFullResource",
		},
		APIOpts: map[string]any{
			utils.OptsContext: utils.MetaSessionS,
		},
	}

	eRply := engine.AttrSProcessEventReply{
		MatchedProfiles: []string{"cgrates.org:ATTR_RESOURCE2"},
		AlteredFields:   []string{utils.MetaReq + utils.NestingSep + "FullResource"},
		CGREvent: &utils.CGREvent{
			Tenant: "cgrates.org",
			ID:     "testAttributeSProcessEventWithResource2",
			Event: map[string]any{
				"EventName":    "AddFullResource",
				"FullResource": "{\"Tenant\":\"cgrates.org\",\"ID\":\"ResTest\",\"Usages\":{\"651a8db2-4f67-4cf8-b622-169e8a482e21\":{\"Tenant\":\"cgrates.org\",\"ID\":\"651a8db2-4f67-4cf8-b622-169e8a482e21\",\"ExpiryTime\":\"2020-10-06T16:12:52.450804203+03:00\",\"Units\":3},\"651a8db2-4f67-4cf8-b622-169e8a482e22\":{\"Tenant\":\"cgrates.org\",\"ID\":\"651a8db2-4f67-4cf8-b622-169e8a482e22\",\"ExpiryTime\":\"2020-10-06T16:12:52.451034151+03:00\",\"Units\":2}},\"TTLIdx\":[\"651a8db2-4f67-4cf8-b622-169e8a482e21\",\"651a8db2-4f67-4cf8-b622-169e8a482e22\"]}",
			},
		},
	}
	sort.Strings(eRply.AlteredFields)
	var rplyEv engine.AttrSProcessEventReply
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
		ev, &rplyEv); err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(rplyEv.MatchedProfiles, eRply.MatchedProfiles) {
		t.Errorf("Expecting: %s, received: %s",
			utils.ToJSON(eRply.MatchedProfiles), utils.ToJSON(rplyEv.MatchedProfiles))
	} else if !reflect.DeepEqual(rplyEv.AlteredFields, eRply.AlteredFields) {
		t.Errorf("Expecting: %s, received: %s",
			utils.ToJSON(eRply.AlteredFields), utils.ToJSON(rplyEv.AlteredFields))
	}
	// some fields are generated(e.g. Time) and compare only some part of the string
	strRes := utils.IfaceAsString(rplyEv.CGREvent.Event["FullResource"])
	if !strings.Contains(strRes, "{\"Tenant\":\"cgrates.org\",\"ID\":\"ResTest\",\"Usages\":{") {
		t.Errorf("Expecting: %s, received: %s",
			"{\"Tenant\":\"cgrates.org\",\"ID\":\"ResTest\",\"Usages\":{", strRes)
	} else if !strings.Contains(strRes, ",\"TTLIdx\":[") {
		t.Errorf("Expecting: %s, received: %s",
			",\"TTLIdx\":[", strRes)
	}
}

func testAttributeSProcessEventWithLibPhoneNumber(t *testing.T) {
	// add new attribute profile
	var result string
	alsPrf := &engine.AttributeProfileWithAPIOpts{
		AttributeProfile: &engine.AttributeProfile{
			Tenant:    "cgrates.org",
			ID:        "ATTR_LIBPHONENUMBER2",
			Contexts:  []string{utils.MetaAny},
			FilterIDs: []string{"*string:~*req.EventName:AddDestinationCarrier"},
			ActivationInterval: &utils.ActivationInterval{
				ActivationTime: time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC),
				ExpiryTime:     time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC),
			},
			Attributes: []*engine.Attribute{
				{
					Path: utils.MetaReq + utils.NestingSep + "DestinationCarrier",
					Type: utils.MetaVariable,
					Value: config.RSRParsers{
						&config.RSRParser{
							Rules: "~*libphonenumber.<~*req.Destination>.Carrier",
						},
					},
				},
			},
			Blocker: false,
			Weight:  10,
		},
	}
	alsPrf.Compile()
	if err := attrRPC.Call(context.Background(), utils.APIerSv1SetAttributeProfile, alsPrf, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	var replyAttr *engine.AttributeProfile
	if err := attrRPC.Call(context.Background(), utils.APIerSv1GetAttributeProfile,
		utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "ATTR_LIBPHONENUMBER2"}}, &replyAttr); err != nil {
		t.Fatal(err)
	}
	replyAttr.Compile()
	if !reflect.DeepEqual(alsPrf.AttributeProfile, replyAttr) {
		t.Errorf("Expecting : %+v, received: %+v", alsPrf.AttributeProfile, replyAttr)
	}

	ev := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "testAttributeSProcessEventWithLibPhoneNumber2",
		Event: map[string]any{
			"EventName":   "AddDestinationCarrier",
			"Destination": "+447779330921",
		},
		APIOpts: map[string]any{
			utils.OptsContext: utils.MetaSessionS,
		},
	}

	eRply := engine.AttrSProcessEventReply{
		MatchedProfiles: []string{"cgrates.org:ATTR_LIBPHONENUMBER2"},
		AlteredFields:   []string{utils.MetaReq + utils.NestingSep + "DestinationCarrier"},
		CGREvent: &utils.CGREvent{
			Tenant: "cgrates.org",
			ID:     "testAttributeSProcessEventWithLibPhoneNumber2",
			Event: map[string]any{
				"EventName":          "AddDestinationCarrier",
				"Destination":        "+447779330921",
				"DestinationCarrier": "Orange",
			},
			APIOpts: map[string]any{
				utils.OptsContext: utils.MetaSessionS,
			},
		},
	}
	sort.Strings(eRply.AlteredFields)
	var rplyEv engine.AttrSProcessEventReply
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
		ev, &rplyEv); err != nil {
		t.Fatal(err)
	}
	sort.Strings(rplyEv.AlteredFields)
	if !reflect.DeepEqual(eRply, rplyEv) {
		t.Errorf("Expecting: %+v, received: %+v",
			utils.ToJSON(eRply), utils.ToJSON(rplyEv))
	}
}

func testAttributeSProcessEventWithLibPhoneNumberComposed(t *testing.T) {
	// add new attribute profile
	var result string
	alsPrf := &engine.AttributeProfileWithAPIOpts{
		AttributeProfile: &engine.AttributeProfile{
			Tenant:    "cgrates.org",
			ID:        "ATTR_LIBPHONENUMBER_COMPOSED",
			Contexts:  []string{utils.MetaAny},
			FilterIDs: []string{"*string:~*req.EventName:AddComposedInfo"},
			ActivationInterval: &utils.ActivationInterval{
				ActivationTime: time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC),
				ExpiryTime:     time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC),
			},
			Attributes: []*engine.Attribute{
				{
					Path: utils.MetaReq + utils.NestingSep + "DestinationCarrier",
					Type: utils.MetaComposed,
					Value: config.RSRParsers{
						&config.RSRParser{
							Rules: "~*libphonenumber.<~*req.Destination>.Carrier",
						},
					},
				},
				{
					Path: utils.MetaReq + utils.NestingSep + "DestinationCarrier",
					Type: utils.MetaComposed,
					Value: config.RSRParsers{
						&config.RSRParser{
							Rules: ";",
						},
					},
				},
				{
					Path: utils.MetaReq + utils.NestingSep + "DestinationCarrier",
					Type: utils.MetaComposed,
					Value: config.RSRParsers{
						&config.RSRParser{
							Rules: "~*libphonenumber.<~*req.Destination>.CountryCode",
						},
					},
				},
			},
			Blocker: false,
			Weight:  10,
		},
	}
	alsPrf.Compile()
	if err := attrRPC.Call(context.Background(), utils.APIerSv1SetAttributeProfile, alsPrf, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	var replyAttr *engine.AttributeProfile
	if err := attrRPC.Call(context.Background(), utils.APIerSv1GetAttributeProfile,
		utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "ATTR_LIBPHONENUMBER_COMPOSED"}}, &replyAttr); err != nil {
		t.Fatal(err)
	}
	replyAttr.Compile()
	if !reflect.DeepEqual(alsPrf.AttributeProfile, replyAttr) {
		t.Errorf("Expecting : %+v, received: %+v", alsPrf.AttributeProfile, replyAttr)
	}

	ev := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "testAttributeSProcessEventWithLibPhoneNumberComposed",
		Event: map[string]any{
			"EventName":   "AddComposedInfo",
			"Destination": "+447779330921",
		},
		APIOpts: map[string]any{
			utils.OptsContext: utils.MetaSessionS,
		},
	}

	eRply := engine.AttrSProcessEventReply{
		MatchedProfiles: []string{"cgrates.org:ATTR_LIBPHONENUMBER_COMPOSED"},
		AlteredFields:   []string{utils.MetaReq + utils.NestingSep + "DestinationCarrier"},
		CGREvent: &utils.CGREvent{
			Tenant: "cgrates.org",
			ID:     "testAttributeSProcessEventWithLibPhoneNumberComposed",
			Event: map[string]any{
				"EventName":          "AddComposedInfo",
				"Destination":        "+447779330921",
				"DestinationCarrier": "Orange;44",
			},
			APIOpts: map[string]any{
				utils.OptsContext: utils.MetaSessionS,
			},
		},
	}
	sort.Strings(eRply.AlteredFields)
	var rplyEv engine.AttrSProcessEventReply
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
		ev, &rplyEv); err != nil {
		t.Fatal(err)
	}
	sort.Strings(rplyEv.AlteredFields)
	if !reflect.DeepEqual(eRply, rplyEv) {
		t.Errorf("Expecting: %+v, received: %+v",
			utils.ToJSON(eRply), utils.ToJSON(rplyEv))
	}
}

func testAttributeSProcessEventWithLibPhoneNumberFull(t *testing.T) {
	// add new attribute profile
	var result string
	alsPrf := &engine.AttributeProfileWithAPIOpts{
		AttributeProfile: &engine.AttributeProfile{
			Tenant:    "cgrates.org",
			ID:        "ATTR_LIBPHONENUMBER",
			Contexts:  []string{utils.MetaAny},
			FilterIDs: []string{"*string:~*req.EventName:AddDestinationDetails"},
			ActivationInterval: &utils.ActivationInterval{
				ActivationTime: time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC),
				ExpiryTime:     time.Date(2014, 7, 14, 14, 35, 0, 0, time.UTC),
			},
			Attributes: []*engine.Attribute{
				{
					Path: utils.MetaReq + utils.NestingSep + "DestinationDetails",
					Type: utils.MetaVariable,
					Value: config.RSRParsers{
						&config.RSRParser{
							Rules: "~*libphonenumber.<~*req.Destination>",
						},
					},
				},
			},
			Blocker: false,
			Weight:  10,
		},
	}
	alsPrf.Compile()
	if err := attrRPC.Call(context.Background(), utils.APIerSv1SetAttributeProfile, alsPrf, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	var replyAttr *engine.AttributeProfile
	if err := attrRPC.Call(context.Background(), utils.APIerSv1GetAttributeProfile,
		utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "ATTR_LIBPHONENUMBER"}}, &replyAttr); err != nil {
		t.Fatal(err)
	}
	replyAttr.Compile()
	if !reflect.DeepEqual(alsPrf.AttributeProfile, replyAttr) {
		t.Errorf("Expecting : %+v, received: %+v", alsPrf.AttributeProfile, replyAttr)
	}

	ev := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "testAttributeSProcessEventWithLibPhoneNumber",
		Event: map[string]any{
			"EventName":   "AddDestinationDetails",
			"Destination": "+447779330921",
		},
		APIOpts: map[string]any{
			utils.OptsContext: utils.MetaSessionS,
		},
	}

	eRply := engine.AttrSProcessEventReply{
		MatchedProfiles: []string{"cgrates.org:ATTR_LIBPHONENUMBER"},
		AlteredFields:   []string{utils.MetaReq + utils.NestingSep + "DestinationDetails"},
		CGREvent: &utils.CGREvent{
			Tenant: "cgrates.org",
			ID:     "testAttributeSProcessEventWithLibPhoneNumber",
			Event: map[string]any{
				"EventName":          "AddDestinationDetails",
				"Destination":        "+447779330921",
				"DestinationDetails": "{\"Carrier\":\"Orange\",\"CountryCode\":44,\"CountryCodeSource\":1,\"Extension\":\"\",\"GeoLocation\":\"United Kingdom\",\"ItalianLeadingZero\":false,\"LengthOfNationalDestinationCode\":4,\"NationalNumber\":7779330921,\"NumberOfLeadingZeros\":1,\"NumberType\":1,\"PreferredDomesticCarrierCode\":\"\",\"RawInput\":\"+447779330921\",\"Region\":\"GB\"}",
			},
			APIOpts: map[string]any{
				utils.OptsContext: utils.MetaSessionS,
			},
		},
	}
	sort.Strings(eRply.AlteredFields)
	var rplyEv engine.AttrSProcessEventReply
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
		ev, &rplyEv); err != nil {
		t.Fatal(err)
	}
	sort.Strings(rplyEv.AlteredFields)
	if !reflect.DeepEqual(eRply, rplyEv) {
		t.Errorf("Expecting: %+v, received: %+v",
			utils.ToJSON(eRply), utils.ToJSON(rplyEv))
	}
}

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

func TestAttributesDestinationFilters(t *testing.T) {
	switch *utils.DBType {
	case utils.MetaInternal:
	case utils.MetaMySQL, utils.MetaMongo, utils.MetaPostgres:
		t.SkipNow()
	default:
		t.Fatal("unsupported dbtype value")
	}

	content := `{

"general": {
	"log_level": 7,
},

"data_db": {								
	"db_type": "*internal"
},

"stor_db": {
	"db_type": "*internal"
},

"attributes": {
	"enabled": true
},

"filters": {			
	"apiers_conns": ["*localhost"]
},

"apiers": {
	"enabled": true,
}

}`

	tpFiles := map[string]string{
		utils.AttributesCsv: `#Tenant,ID,Context,FilterIDs,ActivationInterval,AttributeFilterIDs,Path,Type,Value,Blocker,Weight
cgrates.org,ATTR_INLINE_FILTER,,*string:~*req.Account:1001,,,,,,,30
cgrates.org,ATTR_INLINE_FILTER,,,,*destinations:~*req.Destination:1002,*req.InlinePrefixCase,*constant,shouldnotmatch,,
cgrates.org,ATTR_INLINE_FILTER,,,,*destinations:~*req.Destination:DST_20,*req.InlineWrongDestination,*constant,shouldnotmatch,,
cgrates.org,ATTR_INLINE_FILTER,,,,*destinations:~*req.Destination:DST_20|DST_10,*req.InlineOrDestinationMatch,*constant,shouldmatch,,
cgrates.org,ATTR_INLINE_FILTER,,,,*destinations:~*req.Destination:DST_10,*req.InlineDestinationMatch,*constant,shouldmatch,,
cgrates.org,ATTR_PREDEFINED_FILTER,,*string:~*req.Account:2001,,,,,,,
cgrates.org,ATTR_PREDEFINED_FILTER,,,,FLTR_DESTINATION_DIRECT,*req.PredefinedPrefixCase,*constant,shouldnotmatch,,
cgrates.org,ATTR_PREDEFINED_FILTER,,,,FLTR_WRONG_DESTINATION,*req.PredefinedWrongDestination,*constant,shouldnotmatch,,
cgrates.org,ATTR_PREDEFINED_FILTER,,,,FLTR_OR_DESTINATION_MATCH,*req.PredefinedOrDestinationMatch,*constant,shouldmatch,,
cgrates.org,ATTR_PREDEFINED_FILTER,,,,FLTR_DESTINATION_MATCH,*req.PredefinedDestinationMatch,*constant,shouldmatch,,`,
		utils.DestinationsCsv: `#Id,Prefix
DST_10,10
DST_20,20`,
		utils.FiltersCsv: `#Tenant[0],ID[1],Type[2],Path[3],Values[4],ActivationInterval[5]
cgrates.org,FLTR_DESTINATION_DIRECT,*destinations,~*req.Destination,1002,
cgrates.org,FLTR_WRONG_DESTINATION,*destinations,~*req.Destination,DST_20,
cgrates.org,FLTR_OR_DESTINATION_MATCH,*destinations,~*req.Destination,DST_20;DST_10,
cgrates.org,FLTR_DESTINATION_MATCH,*destinations,~*req.Destination,DST_10,`,
	}

	ng := engine.TestEngine{
		ConfigJSON: content,
		TpFiles:    tpFiles,
	}
	client, _ := ng.Run(t)

	t.Run("SetAttributesThroughAPI", func(t *testing.T) {
		var reply string
		attrPrf := &engine.AttributeProfileWithAPIOpts{
			AttributeProfile: &engine.AttributeProfile{
				Tenant:    "cgrates.org",
				ID:        "ATTR_API",
				FilterIDs: []string{"*string:~*req.Account:3001"},
				Contexts:  []string{utils.MetaAny},
				Attributes: []*engine.Attribute{
					{
						FilterIDs: []string{"FLTR_DESTINATION_DIRECT", "*destinations:~*req.Destination:1002"},
						Path:      "*req.PrefixCase",
						Type:      utils.MetaConstant,
						Value:     config.NewRSRParsersMustCompile("shouldnotmatch", utils.InfieldSep),
					},
					{
						FilterIDs: []string{"FLTR_WRONG_DESTINATION", "*destinations:~*req.Destination:DST_20"},
						Path:      "*req.WrongDestination",
						Type:      utils.MetaConstant,
						Value:     config.NewRSRParsersMustCompile("shouldnotmatch", utils.InfieldSep),
					},
					{
						FilterIDs: []string{"FLTR_OR_DESTINATION_MATCH", "*destinations:~*req.Destination:DST_20|DST_10"},
						Path:      "*req.OrDestinationMatch",
						Type:      utils.MetaConstant,
						Value:     config.NewRSRParsersMustCompile("shouldmatch", utils.InfieldSep),
					},
					{
						FilterIDs: []string{"FLTR_DESTINATION_MATCH", "*destinations:~*req.Destination:DST_10"},
						Path:      "*req.DestinationMatch",
						Type:      utils.MetaConstant,
						Value:     config.NewRSRParsersMustCompile("shouldmatch", utils.InfieldSep),
					},
				},
				Weight: 10,
			},
		}
		attrPrf.Compile()
		if err := client.Call(context.Background(), utils.APIerSv1SetAttributeProfile, attrPrf, &reply); err != nil {
			t.Error(err)
		}
	})

	t.Run("CheckFieldsAlteredByAttributeS", func(t *testing.T) {
		ev := &utils.CGREvent{
			Tenant: "cgrates.org",
			ID:     "event_test",
			Event: map[string]any{
				utils.AccountField: "1001",
				utils.Destination:  "1002",
			},
		}
		expected := engine.AttrSProcessEventReply{
			MatchedProfiles: []string{"cgrates.org:ATTR_INLINE_FILTER"},
			AlteredFields:   []string{"*req.InlineDestinationMatch", "*req.InlineOrDestinationMatch"},
			CGREvent: &utils.CGREvent{
				Tenant: "cgrates.org",
				ID:     "event_test",
				Event: map[string]any{
					utils.AccountField:         "1001",
					utils.Destination:          "1002",
					"InlineDestinationMatch":   "shouldmatch",
					"InlineOrDestinationMatch": "shouldmatch",
				},
				APIOpts: make(map[string]any),
			},
		}

		var reply engine.AttrSProcessEventReply
		if err := client.Call(context.Background(), utils.AttributeSv1ProcessEvent,
			ev, &reply); err != nil {
			t.Error(err)
		} else {
			sort.Strings(expected.AlteredFields)
			sort.Strings(reply.AlteredFields)
			if !reflect.DeepEqual(expected, reply) {
				t.Errorf("\nexpected: %s, \nreceived: %s",
					utils.ToJSON(expected), utils.ToJSON(reply))
			}
		}

		ev.Event = map[string]any{
			utils.AccountField: "2001",
			utils.Destination:  "1002",
		}
		expected.MatchedProfiles = []string{"cgrates.org:ATTR_PREDEFINED_FILTER"}
		expected.AlteredFields = []string{"*req.PredefinedDestinationMatch", "*req.PredefinedOrDestinationMatch"}
		expected.CGREvent.Event = map[string]any{
			utils.AccountField:             "2001",
			utils.Destination:              "1002",
			"PredefinedDestinationMatch":   "shouldmatch",
			"PredefinedOrDestinationMatch": "shouldmatch",
		}

		reply = engine.AttrSProcessEventReply{}
		if err := client.Call(context.Background(), utils.AttributeSv1ProcessEvent,
			ev, &reply); err != nil {
			t.Error(err)
		} else {
			sort.Strings(expected.AlteredFields)
			sort.Strings(reply.AlteredFields)
			if !reflect.DeepEqual(expected, reply) {
				t.Errorf("\nexpected: %s, \nreceived: %s",
					utils.ToJSON(expected), utils.ToJSON(reply))
			}
		}

		ev.Event = map[string]any{
			utils.AccountField: "3001",
			utils.Destination:  "1002",
		}
		expected.MatchedProfiles = []string{"cgrates.org:ATTR_API"}
		expected.AlteredFields = []string{"*req.DestinationMatch", "*req.OrDestinationMatch"}
		expected.CGREvent.Event = map[string]any{
			utils.AccountField:   "3001",
			utils.Destination:    "1002",
			"DestinationMatch":   "shouldmatch",
			"OrDestinationMatch": "shouldmatch",
		}

		reply = engine.AttrSProcessEventReply{}
		if err := client.Call(context.Background(), utils.AttributeSv1ProcessEvent,
			ev, &reply); err != nil {
			t.Error(err)
		} else {
			sort.Strings(expected.AlteredFields)
			sort.Strings(reply.AlteredFields)
			if !reflect.DeepEqual(expected, reply) {
				t.Errorf("\nexpected: %s, \nreceived: %s",
					utils.ToJSON(expected), utils.ToJSON(reply))
			}
		}
	})
}

func TestAttributesArith(t *testing.T) {
	switch *utils.DBType {
	case utils.MetaInternal:
	case utils.MetaMySQL, utils.MetaMongo, utils.MetaPostgres:
		t.SkipNow()
	default:
		t.Fatal("unsupported dbtype value")
	}

	content := `{

"general": {
	"log_level": 7,
},

"data_db": {								
	"db_type": "*internal"
},

"stor_db": {
	"db_type": "*internal"
},

"attributes": {
	"enabled": true
},

"apiers": {
	"enabled": true,
}

}`

	tpFiles := map[string]string{
		utils.AttributesCsv: `#Tenant,ID,Context,FilterIDs,ActivationInterval,AttributeFilterIDs,Path,Type,Value,Blocker,Weight
cgrates.org,ATTR_ARITH,,*string:~*req.AttrSource:csv,,,,,,,
cgrates.org,ATTR_ARITH,,,,,*req.3*4,*multiply,3;4,,
cgrates.org,ATTR_ARITH,,,,,*req.12/4,*divide,12;4,,
cgrates.org,ATTR_ARITH,,,,,*req.3+4,*sum,3;4,,
cgrates.org,ATTR_ARITH,,,,,*req.3-4,*difference,3;4,,
cgrates.org,ATTR_ARITH,,,,,*req.MultiplyBetweenVariables,*multiply,~*req.Elem1;~*req.Elem2,,`,
	}

	ng := engine.TestEngine{
		ConfigJSON: content,
		TpFiles:    tpFiles,
	}
	client, _ := ng.Run(t)

	t.Run("SetAttributesThroughAPI", func(t *testing.T) {
		var reply string
		attrPrf := &engine.AttributeProfileWithAPIOpts{
			AttributeProfile: &engine.AttributeProfile{
				Tenant:    "cgrates.org",
				ID:        "ATTR_API",
				FilterIDs: []string{"*string:~*req.AttrSource:api"},
				Contexts:  []string{utils.MetaAny},
				Attributes: []*engine.Attribute{
					{
						Path:  "*req.3*4",
						Type:  utils.MetaMultiply,
						Value: config.NewRSRParsersMustCompile("3;4", utils.InfieldSep),
					},
					{
						Path:  "*req.12/4",
						Type:  utils.MetaDivide,
						Value: config.NewRSRParsersMustCompile("12;4", utils.InfieldSep),
					},
					{
						Path:  "*req.3+4",
						Type:  utils.MetaSum,
						Value: config.NewRSRParsersMustCompile("3;4", utils.InfieldSep),
					},
					{
						Path:  "*req.3-4",
						Type:  utils.MetaDifference,
						Value: config.NewRSRParsersMustCompile("3;4", utils.InfieldSep),
					},
					{
						Path:  "*req.MultiplyBetweenVariables",
						Type:  utils.MetaMultiply,
						Value: config.NewRSRParsersMustCompile("~*req.Elem1;~*req.Elem2", utils.InfieldSep),
					},
				},
				Weight: 10,
			},
		}
		attrPrf.Compile()
		if err := client.Call(context.Background(), utils.APIerSv1SetAttributeProfile, attrPrf, &reply); err != nil {
			t.Error(err)
		}
	})

	t.Run("CheckFieldsAlteredByAttributeS", func(t *testing.T) {
		ev := &utils.CGREvent{
			Tenant: "cgrates.org",
			ID:     "event_test",
			Event: map[string]any{
				"AttrSource": "csv",
				"Elem1":      "3",
				"Elem2":      "4",
			},
		}
		expected := engine.AttrSProcessEventReply{
			MatchedProfiles: []string{"cgrates.org:ATTR_ARITH"},
			AlteredFields:   []string{"*req.12/4", "*req.3*4", "*req.3+4", "*req.3-4", "*req.MultiplyBetweenVariables"},
			CGREvent: &utils.CGREvent{
				Tenant: "cgrates.org",
				ID:     "event_test",
				Event: map[string]any{
					"AttrSource":               "csv",
					"Elem1":                    "3",
					"Elem2":                    "4",
					"12/4":                     "3",
					"3*4":                      "12",
					"3+4":                      "7",
					"3-4":                      "-1",
					"MultiplyBetweenVariables": "12",
				},
				APIOpts: make(map[string]any),
			},
		}

		var reply engine.AttrSProcessEventReply
		if err := client.Call(context.Background(), utils.AttributeSv1ProcessEvent,
			ev, &reply); err != nil {
			t.Error(err)
		} else {
			sort.Strings(expected.AlteredFields)
			sort.Strings(reply.AlteredFields)
			if !reflect.DeepEqual(expected, reply) {
				t.Errorf("\nexpected: %s, \nreceived: %s",
					utils.ToJSON(expected), utils.ToJSON(reply))
			}
		}

		ev.Event["AttrSource"] = "api"
		expected.MatchedProfiles = []string{"cgrates.org:ATTR_API"}
		expected.AlteredFields = []string{"*req.12/4", "*req.3*4", "*req.3+4", "*req.3-4", "*req.MultiplyBetweenVariables"}
		expected.CGREvent.Event = map[string]any{
			"AttrSource":               "api",
			"Elem1":                    "3",
			"Elem2":                    "4",
			"12/4":                     "3",
			"3*4":                      "12",
			"3+4":                      "7",
			"3-4":                      "-1",
			"MultiplyBetweenVariables": "12",
		}

		reply = engine.AttrSProcessEventReply{}
		if err := client.Call(context.Background(), utils.AttributeSv1ProcessEvent,
			ev, &reply); err != nil {
			t.Error(err)
		} else {
			sort.Strings(expected.AlteredFields)
			sort.Strings(reply.AlteredFields)
			if !reflect.DeepEqual(expected, reply) {
				t.Errorf("\nexpected: %s, \nreceived: %s",
					utils.ToJSON(expected), utils.ToJSON(reply))
			}
		}
	})
}
