//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 attributes

import (
	"path"
	"reflect"
	"slices"
	"sort"
	"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
	attrConfigDIR string //run tests for specific configuration

	sTestsAttr = []func(t *testing.T){
		testAttributesInitCfg,
		testAttributesInitDataDb,
		testAttributesStartEngine,
		testAttributesRPCConn,

		// tests for AdminSv1 APIs
		testAttributesGetAttributeProfileBeforeSet,
		testAttributesGetAttributeProfileIDsBeforeSet,
		testAttributesGetAttributeProfileCountBeforeSet,
		testAttributesGetAttributeProfilesBeforeSet,
		testAttributesSetAttributeProfiles,
		testAttributesGetAttributeProfileAfterSet,
		testAttributesGetAttributeProfileIDsAfterSet,
		testAttributesGetAttributeProfileCountAfterSet,
		testAttributesGetAttributeProfilesAfterSet,
		testAttributesRemoveAttributeProfile,
		testAttributesGetAttributeProfileAfterRemove,
		testAttributesGetAttributeProfileIDsAfterRemove,
		testAttributesGetAttributeProfileCountAfterRemove,
		testAttributesGetAttributeProfilesAfterRemove,

		//tests for AttributeSv1 APIs
		testAttributeSetAttributeProfileBrokenReference,
		testAttributeSGetAttributeForEventMissingEvent,
		testAttributeSGetAttributeForEventAnyContext,
		testAttributeSGetAttributeForEventSameAnyContext,
		testAttributeSGetAttributeForEventNotFound,
		testAttributeSGetAttributeForEvent,
		testAttributeSetAttributeProfile,
		testAttributeProcessEvent,
		testAttributeProcessEventWithSearchAndReplace,
		testAttributeSProcessWithMultipleRuns,
		testAttributeSProcessWithMultipleRuns2,
		testAttributeGetAttributeProfileAllIDs,
		testAttributeGetAttributeProfileAllCount,
		testAttributeRemoveRemainAttributeProfiles,
		testAttributeGetAttributeProfileAfterRemove,

		// tests for attribute blocker behaviour
		testAttributeSetAttributeProfileWithAttrBlockers,
		testAttributeSetAttributeProfileWithAttrBlockers2,
		testAttributeSetAttributeProfileBlockersBothProfilesProcessRuns,

		// Testing index behaviour
		testAttributeSSetNonIndexedTypeFilter,
		testAttributeSSetIndexedTypeFilter,
		testAttributeSClearIndexes,
		testAttributeSCheckIndexesSetAttributeProfileWithoutFilters,

		// testAttributeSCheckIndexesAddNonIndexedFilter,
		testAttributeSCheckIndexesAddIndexedFilters,
		testAttributeSCheckIndexesModifyIndexedFilter,
		testAttributeSCheckIndexesRemoveAnIndexedFilter,
		testAttributeSCheckIndexesRemoveAttributeProfile,

		testAttributesKillEngine,
	}
)

func TestAttributesIT(t *testing.T) {
	switch *utils.DBType {
	case utils.MetaInternal:
		attrConfigDIR = "attributes_internal"
	case utils.MetaMongo:
		attrConfigDIR = "attributes_mongo"
	case utils.MetaRedis:
		attrConfigDIR = "attributes_redis"
	case utils.MetaMySQL:
		attrConfigDIR = "attributes_mysql"
	case utils.MetaPostgres:
		attrConfigDIR = "attributes_postgres"
	default:
		t.Fatal("Unknown Database type")
	}
	for _, stest := range sTestsAttr {
		t.Run(attrConfigDIR, stest)
	}
}

func testAttributesInitCfg(t *testing.T) {
	var err error
	attrCfgPath = path.Join(*utils.DataDir, "conf", "samples", attrConfigDIR)
	attrCfg, err = config.NewCGRConfigFromPath(context.Background(), attrCfgPath)
	if err != nil {
		t.Error(err)
	}
}

func testAttributesInitDataDb(t *testing.T) {
	if err := engine.InitDB(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)
	}
}

func testAttributesRPCConn(t *testing.T) {
	attrRPC = engine.NewRPCClient(t, attrCfg.ListenCfg(), *utils.Encoding)
}

func testAttributesGetAttributeProfileBeforeSet(t *testing.T) {
	var replyAttributeProfile utils.APIAttributeProfile
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
		&utils.TenantIDWithAPIOpts{
			TenantID: &utils.TenantID{
				Tenant: "cgrates.org",
				ID:     "TestA_ATTRIBUTE1",
			}}, &replyAttributeProfile); err == nil || err.Error() != utils.ErrNotFound.Error() {
		t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ErrNotFound, err)
	}
}

func testAttributesGetAttributeProfilesBeforeSet(t *testing.T) {
	var replyAttributeProfiles *[]*utils.APIAttributeProfile
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfiles,
		&utils.ArgsItemIDs{
			Tenant: "cgrates.org",
		}, &replyAttributeProfiles); err == nil || err.Error() != utils.ErrNotFound.Error() {
		t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ErrNotFound, err)
	}
}

func testAttributesGetAttributeProfileIDsBeforeSet(t *testing.T) {
	var replyAttributeProfileIDs []string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfileIDs,
		&utils.ArgsItemIDs{
			Tenant: "cgrates.org",
		}, &replyAttributeProfileIDs); err == nil || err.Error() != utils.ErrNotFound.Error() {
		t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ErrNotFound, err)
	}
}

func testAttributesGetAttributeProfileCountBeforeSet(t *testing.T) {
	var replyCount int
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfilesCount,
		&utils.ArgsItemIDs{
			Tenant: "cgrates.org",
		}, &replyCount); err == nil || err.Error() != utils.ErrNotFound.Error() {
		t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ErrNotFound, err)
	} else if replyCount != 0 {
		t.Errorf("expected <%+v>, \nreceived: <%+v>", 0, replyCount)
	}
}

func testAttributesSetAttributeProfiles(t *testing.T) {
	attributeProfiles := []*utils.APIAttributeProfileWithAPIOpts{
		{
			APIAttributeProfile: &utils.APIAttributeProfile{
				ID:        "TestA_ATTRIBUTE1",
				Tenant:    "cgrates.org",
				FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
				Weights: utils.DynamicWeights{
					{
						Weight: 30,
					},
				},
				Blockers: utils.DynamicBlockers{
					{
						Blocker: false,
					},
				},
				Attributes: []*utils.ExternalAttribute{
					{
						Path:      "*req.Account",
						Type:      utils.MetaConstant,
						Value:     "1001",
						FilterIDs: []string{"fltr1"},
					},
				},
			},
		},
		{
			APIAttributeProfile: &utils.APIAttributeProfile{
				ID:        "TestA_ATTRIBUTE2",
				Tenant:    "cgrates.org",
				FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
				Weights: utils.DynamicWeights{
					{
						Weight: 10,
					},
				},
				Blockers: utils.DynamicBlockers{
					{
						Blocker: true,
					},
				},
				Attributes: []*utils.ExternalAttribute{
					{
						Path:      "*req.Account",
						Type:      utils.MetaConstant,
						Value:     "1002",
						FilterIDs: []string{"fltr2"},
					},
				},
			},
		},
		{
			APIAttributeProfile: &utils.APIAttributeProfile{
				ID:        "TestA_ATTRIBUTE3",
				Tenant:    "cgrates.org",
				FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
				Weights: utils.DynamicWeights{
					{
						Weight: 20,
					},
				},
				Attributes: []*utils.ExternalAttribute{
					{
						Path:      "*req.Account",
						Type:      utils.MetaConstant,
						Value:     "1003",
						FilterIDs: []string{"fltr3"},
					},
				},
			},
		},
		{
			APIAttributeProfile: &utils.APIAttributeProfile{
				ID:        "TestB_ATTRIBUTE1",
				Tenant:    "cgrates.org",
				FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
				Weights: utils.DynamicWeights{
					{
						Weight: 5,
					},
				},
				Attributes: []*utils.ExternalAttribute{
					{
						Path:      "*req.Account",
						Type:      utils.MetaConstant,
						Value:     "2001",
						FilterIDs: []string{"fltr4"},
					},
				},
			},
		},
		{
			APIAttributeProfile: &utils.APIAttributeProfile{
				ID:        "TestB_ATTRIBUTE2",
				Tenant:    "cgrates.org",
				FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
				Weights: utils.DynamicWeights{
					{
						Weight: 25,
					},
				},
				Attributes: []*utils.ExternalAttribute{
					{
						Path:      "*req.Account",
						Type:      utils.MetaConstant,
						Value:     "2002",
						FilterIDs: []string{"fltr5"},
					},
				},
			},
		},
	}

	var reply string
	for _, attributeProfile := range attributeProfiles {
		if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
			attributeProfile, &reply); err != nil {
			t.Error(err)
		} else if reply != utils.OK {
			t.Error(err)
		}
	}
}

func testAttributesGetAttributeProfileAfterSet(t *testing.T) {
	expectedAttributeProfile := utils.APIAttributeProfile{
		ID:        "TestA_ATTRIBUTE1",
		Tenant:    "cgrates.org",
		FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
		Weights: utils.DynamicWeights{
			{
				Weight: 30,
			},
		},
		Blockers: utils.DynamicBlockers{
			{
				Blocker: false,
			},
		},
		Attributes: []*utils.ExternalAttribute{
			{
				Path:      "*req.Account",
				Type:      utils.MetaConstant,
				Value:     "1001",
				FilterIDs: []string{"fltr1"},
			},
		},
	}
	var replyAttributeProfile utils.APIAttributeProfile
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
		&utils.TenantIDWithAPIOpts{
			TenantID: &utils.TenantID{
				Tenant: "cgrates.org",
				ID:     "TestA_ATTRIBUTE1",
			}}, &replyAttributeProfile); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(replyAttributeProfile, expectedAttributeProfile) {
		t.Errorf("expected: <%+v>, \nreceived: <%+v>",
			utils.ToJSON(expectedAttributeProfile), utils.ToJSON(replyAttributeProfile))
	}
}

func testAttributesGetAttributeProfileIDsAfterSet(t *testing.T) {
	expectedIDs := []string{"TestA_ATTRIBUTE1", "TestA_ATTRIBUTE2", "TestA_ATTRIBUTE3", "TestB_ATTRIBUTE1", "TestB_ATTRIBUTE2"}
	var replyAttributeProfileIDs []string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfileIDs,
		&utils.ArgsItemIDs{
			Tenant: "cgrates.org",
		}, &replyAttributeProfileIDs); err != nil {
		t.Error(err)
	} else {
		sort.Strings(replyAttributeProfileIDs)
		if !slices.Equal(replyAttributeProfileIDs, expectedIDs) {
			t.Errorf("expected: <%+v>, \nreceived: <%+v>", expectedIDs, replyAttributeProfileIDs)
		}
	}

	expectedIDs = []string{"TestA_ATTRIBUTE1", "TestA_ATTRIBUTE2", "TestA_ATTRIBUTE3"}
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfileIDs,
		&utils.ArgsItemIDs{
			Tenant:      "cgrates.org",
			ItemsPrefix: "TestA",
		}, &replyAttributeProfileIDs); err != nil {
		t.Error(err)
	} else {
		sort.Strings(replyAttributeProfileIDs)
		if !slices.Equal(replyAttributeProfileIDs, expectedIDs) {
			t.Errorf("expected: <%+v>, \nreceived: <%+v>", expectedIDs, replyAttributeProfileIDs)
		}
	}

	expectedIDs = []string{"TestB_ATTRIBUTE1", "TestB_ATTRIBUTE2"}
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfileIDs,
		&utils.ArgsItemIDs{
			Tenant:      "cgrates.org",
			ItemsPrefix: "TestB",
		}, &replyAttributeProfileIDs); err != nil {
		t.Error(err)
	} else {
		sort.Strings(replyAttributeProfileIDs)
		if !slices.Equal(replyAttributeProfileIDs, expectedIDs) {
			t.Errorf("expected: <%+v>, \nreceived: <%+v>", expectedIDs, replyAttributeProfileIDs)
		}
	}
}

func testAttributesGetAttributeProfileCountAfterSet(t *testing.T) {
	var replyCount int
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfilesCount,
		&utils.ArgsItemIDs{
			Tenant: "cgrates.org",
		}, &replyCount); err != nil {
		t.Error(err)
	} else if replyCount != 5 {
		t.Errorf("expected <%+v>, \nreceived: <%+v>", 0, replyCount)
	}

	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfilesCount,
		&utils.ArgsItemIDs{
			Tenant:      "cgrates.org",
			ItemsPrefix: "TestA",
		}, &replyCount); err != nil {
		t.Error(err)
	} else if replyCount != 3 {
		t.Errorf("expected <%+v>, \nreceived: <%+v>", 0, replyCount)
	}

	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfilesCount,
		&utils.ArgsItemIDs{
			Tenant:      "cgrates.org",
			ItemsPrefix: "TestB",
		}, &replyCount); err != nil {
		t.Error(err)
	} else if replyCount != 2 {
		t.Errorf("expected <%+v>, \nreceived: <%+v>", 0, replyCount)
	}
}

func testAttributesGetAttributeProfilesAfterSet(t *testing.T) {
	expectedAttributeProfiles := []*utils.APIAttributeProfile{
		{
			ID:        "TestA_ATTRIBUTE1",
			Tenant:    "cgrates.org",
			FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
			Weights: utils.DynamicWeights{
				{
					Weight: 30,
				},
			},
			Blockers: utils.DynamicBlockers{
				{
					Blocker: false,
				},
			},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:      "*req.Account",
					Type:      utils.MetaConstant,
					Value:     "1001",
					FilterIDs: []string{"fltr1"},
				},
			},
		},
		{
			ID:        "TestA_ATTRIBUTE2",
			Tenant:    "cgrates.org",
			FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
			Weights: utils.DynamicWeights{
				{
					Weight: 10,
				},
			},
			Blockers: utils.DynamicBlockers{
				{
					Blocker: true,
				},
			},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:      "*req.Account",
					Type:      utils.MetaConstant,
					Value:     "1002",
					FilterIDs: []string{"fltr2"},
				},
			},
		},
		{
			ID:        "TestA_ATTRIBUTE3",
			Tenant:    "cgrates.org",
			FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
			Weights: utils.DynamicWeights{
				{
					Weight: 20,
				},
			},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:      "*req.Account",
					Type:      utils.MetaConstant,
					Value:     "1003",
					FilterIDs: []string{"fltr3"},
				},
			},
		},
		{
			ID:        "TestB_ATTRIBUTE1",
			Tenant:    "cgrates.org",
			FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
			Weights: utils.DynamicWeights{
				{
					Weight: 5,
				},
			},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:      "*req.Account",
					Type:      utils.MetaConstant,
					Value:     "2001",
					FilterIDs: []string{"fltr4"},
				},
			},
		},
		{
			ID:        "TestB_ATTRIBUTE2",
			Tenant:    "cgrates.org",
			FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
			Weights: utils.DynamicWeights{
				{
					Weight: 25,
				},
			},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:      "*req.Account",
					Type:      utils.MetaConstant,
					Value:     "2002",
					FilterIDs: []string{"fltr5"},
				},
			},
		},
	}
	var replyAttributeProfiles []*utils.APIAttributeProfile
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfiles,
		&utils.ArgsItemIDs{
			Tenant: "cgrates.org",
		}, &replyAttributeProfiles); err != nil {
		t.Error(err)
	} else {
		sort.Slice(replyAttributeProfiles, func(i, j int) bool {
			return replyAttributeProfiles[i].ID < replyAttributeProfiles[j].ID
		})
		if !reflect.DeepEqual(replyAttributeProfiles, expectedAttributeProfiles) {
			t.Errorf("expected: <%+v>, \nreceived: <%+v>",
				utils.ToJSON(expectedAttributeProfiles), utils.ToJSON(replyAttributeProfiles))
		}
	}
}

func testAttributesRemoveAttributeProfile(t *testing.T) {
	var reply string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1RemoveAttributeProfile,
		&utils.TenantIDWithAPIOpts{
			TenantID: &utils.TenantID{
				Tenant: "cgrates.org",
				ID:     "TestA_ATTRIBUTE2",
			}}, &reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Error("Unexpected reply returned:", reply)
	}
}

func testAttributesGetAttributeProfileAfterRemove(t *testing.T) {
	var replyAttributeProfile utils.APIAttributeProfile
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
		&utils.TenantIDWithAPIOpts{
			TenantID: &utils.TenantID{
				Tenant: "cgrates.org",
				ID:     "TestA_Attribute2",
			}}, &replyAttributeProfile); err == nil || err.Error() != utils.ErrNotFound.Error() {
		t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ErrNotFound, err)
	}
}

func testAttributesGetAttributeProfileIDsAfterRemove(t *testing.T) {
	expectedIDs := []string{"TestA_ATTRIBUTE1", "TestA_ATTRIBUTE3", "TestB_ATTRIBUTE1", "TestB_ATTRIBUTE2"}
	var replyAttributeProfileIDs []string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfileIDs,
		&utils.ArgsItemIDs{
			Tenant: "cgrates.org",
		}, &replyAttributeProfileIDs); err != nil {
		t.Error(err)
	} else {
		sort.Strings(replyAttributeProfileIDs)
		if !slices.Equal(replyAttributeProfileIDs, expectedIDs) {
			t.Errorf("expected: <%+v>, \nreceived: <%+v>", expectedIDs, replyAttributeProfileIDs)
		}
	}

	expectedIDs = []string{"TestA_ATTRIBUTE1", "TestA_ATTRIBUTE3"}
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfileIDs,
		&utils.ArgsItemIDs{
			Tenant:      "cgrates.org",
			ItemsPrefix: "TestA",
		}, &replyAttributeProfileIDs); err != nil {
		t.Error(err)
	} else {
		sort.Strings(replyAttributeProfileIDs)
		if !slices.Equal(replyAttributeProfileIDs, expectedIDs) {
			t.Errorf("expected: <%+v>, \nreceived: <%+v>", expectedIDs, replyAttributeProfileIDs)
		}
	}

	expectedIDs = []string{"TestB_ATTRIBUTE1", "TestB_ATTRIBUTE2"}
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfileIDs,
		&utils.ArgsItemIDs{
			Tenant:      "cgrates.org",
			ItemsPrefix: "TestB",
		}, &replyAttributeProfileIDs); err != nil {
		t.Error(err)
	} else {
		sort.Strings(replyAttributeProfileIDs)
		if !slices.Equal(replyAttributeProfileIDs, expectedIDs) {
			t.Errorf("expected: <%+v>, \nreceived: <%+v>", expectedIDs, replyAttributeProfileIDs)
		}
	}
}

func testAttributesGetAttributeProfileCountAfterRemove(t *testing.T) {
	var replyCount int
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfilesCount,
		&utils.ArgsItemIDs{
			Tenant: "cgrates.org",
		}, &replyCount); err != nil {
		t.Error(err)
	} else if replyCount != 4 {
		t.Errorf("expected <%+v>, \nreceived: <%+v>", 0, replyCount)
	}

	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfilesCount,
		&utils.ArgsItemIDs{
			Tenant:      "cgrates.org",
			ItemsPrefix: "TestA",
		}, &replyCount); err != nil {
		t.Error(err)
	} else if replyCount != 2 {
		t.Errorf("expected <%+v>, \nreceived: <%+v>", 0, replyCount)
	}

	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfilesCount,
		&utils.ArgsItemIDs{
			Tenant:      "cgrates.org",
			ItemsPrefix: "TestB",
		}, &replyCount); err != nil {
		t.Error(err)
	} else if replyCount != 2 {
		t.Errorf("expected <%+v>, \nreceived: <%+v>", 0, replyCount)
	}
}

func testAttributesGetAttributeProfilesAfterRemove(t *testing.T) {
	expectedAttributeProfiles := []*utils.APIAttributeProfile{
		{
			ID:        "TestA_ATTRIBUTE1",
			Tenant:    "cgrates.org",
			FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
			Weights: utils.DynamicWeights{
				{
					Weight: 30,
				},
			},
			Blockers: utils.DynamicBlockers{
				{
					Blocker: false,
				},
			},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:      "*req.Account",
					Type:      utils.MetaConstant,
					Value:     "1001",
					FilterIDs: []string{"fltr1"},
				},
			},
		},
		{
			ID:        "TestA_ATTRIBUTE3",
			Tenant:    "cgrates.org",
			FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
			Weights: utils.DynamicWeights{
				{
					Weight: 20,
				},
			},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:      "*req.Account",
					Type:      utils.MetaConstant,
					Value:     "1003",
					FilterIDs: []string{"fltr3"},
				},
			},
		},
		{
			ID:        "TestB_ATTRIBUTE1",
			Tenant:    "cgrates.org",
			FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
			Weights: utils.DynamicWeights{
				{
					Weight: 5,
				},
			},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:      "*req.Account",
					Type:      utils.MetaConstant,
					Value:     "2001",
					FilterIDs: []string{"fltr4"},
				},
			},
		},
		{
			ID:        "TestB_ATTRIBUTE2",
			Tenant:    "cgrates.org",
			FilterIDs: []string{"*string:~*req.TestCase:AdminSAPIs"},
			Weights: utils.DynamicWeights{
				{
					Weight: 25,
				},
			},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:      "*req.Account",
					Type:      utils.MetaConstant,
					Value:     "2002",
					FilterIDs: []string{"fltr5"},
				},
			},
		},
	}
	var replyAttributeProfiles []*utils.APIAttributeProfile
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfiles,
		&utils.ArgsItemIDs{
			Tenant: "cgrates.org",
		}, &replyAttributeProfiles); err != nil {
		t.Error(err)
	} else {
		sort.Slice(replyAttributeProfiles, func(i, j int) bool {
			return replyAttributeProfiles[i].ID < replyAttributeProfiles[j].ID
		})
		if !reflect.DeepEqual(replyAttributeProfiles, expectedAttributeProfiles) {
			t.Errorf("expected: <%+v>, \nreceived: <%+v>",
				utils.ToJSON(expectedAttributeProfiles), utils.ToJSON(replyAttributeProfiles))
		}
	}
}

func testAttributeSetAttributeProfileBrokenReference(t *testing.T) {
	attrPrf := &utils.APIAttributeProfileWithAPIOpts{
		APIAttributeProfile: &utils.APIAttributeProfile{
			Tenant:    utils.CGRateSorg,
			ID:        "TEST_ATTRIBUTES_IT_TEST_SECOND",
			FilterIDs: []string{"invalid_filter_format", "*string:~*opts.*context:*sessions"},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:  "*tenant",
					Type:  utils.MetaConstant,
					Value: "cgrates.itsyscom",
				},
			},
		},
	}
	var reply string
	expectedErr := "SERVER_ERROR: broken reference to filter: <invalid_filter_format> for item with ID: cgrates.org:TEST_ATTRIBUTES_IT_TEST_SECOND"
	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
		attrPrf, &reply); err == nil || err.Error() != expectedErr {
		t.Errorf("Expected %+v \n, received %+v", expectedErr, err)
	}
}

func testAttributeSGetAttributeForEventMissingEvent(t *testing.T) {
	var rplyEv AttrSProcessEventReply
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
		nil, &rplyEv); err == nil ||
		err.Error() != utils.ErrNotFound.Error() {
		t.Error(err)
	}
}

func testAttributeSGetAttributeForEventAnyContext(t *testing.T) {
	ev := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "testAttributeSGetAttributeForEventWihMetaAnyContext",
		Event: map[string]any{
			utils.AccountField: "dan",
			utils.Destination:  "+491511231234",
			utils.ToR:          "*voice",
		},
		APIOpts: map[string]any{
			utils.MetaUsage:   10 * time.Second,
			utils.OptsContext: utils.MetaCDRs,
		},
	}
	eAttrPrf2 := &utils.APIAttributeProfileWithAPIOpts{
		APIAttributeProfile: &utils.APIAttributeProfile{
			Tenant:    ev.Tenant,
			ID:        "ATTR_2",
			FilterIDs: []string{"*string:~*req.Account:dan", "*prefix:~*req.Destination:+4915", "*exists:~*opts.*usage:", "*notexists:~*req.RequestType:"},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:  utils.MetaReq + utils.NestingSep + utils.AccountField,
					Type:  utils.MetaConstant,
					Value: "1001",
				},
			},
			Weights: utils.DynamicWeights{
				{
					Weight: 20,
				},
			},
		},
	}
	var result string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
		eAttrPrf2, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	var reply *utils.APIAttributeProfile
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
		utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "ATTR_2"}}, &reply); err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(eAttrPrf2.APIAttributeProfile, reply) {
		t.Errorf("Expecting : %+v, received: %+v", eAttrPrf2.APIAttributeProfile, reply)
	}
	var attrReply *utils.APIAttributeProfile
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1GetAttributeForEvent,
		ev, &attrReply); err != nil {
		t.Fatal(err)
	}

	expAttrFromEv := &utils.APIAttributeProfile{
		Tenant:    ev.Tenant,
		ID:        "ATTR_2",
		FilterIDs: []string{"*string:~*req.Account:dan", "*prefix:~*req.Destination:+4915", "*exists:~*opts.*usage:", "*notexists:~*req.RequestType:"},
		Attributes: []*utils.ExternalAttribute{
			{
				Path:  utils.MetaReq + utils.NestingSep + utils.AccountField,
				Type:  utils.MetaConstant,
				Value: "1001",
			},
		},
		Weights: utils.DynamicWeights{
			{
				Weight: 20,
			},
		},
	}
	if !reflect.DeepEqual(expAttrFromEv, attrReply) {
		t.Errorf("Expecting: %s, received: %s", utils.ToJSON(expAttrFromEv), utils.ToJSON(attrReply))
	}
}

func testAttributeSGetAttributeForEventSameAnyContext(t *testing.T) {
	ev := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "testAttributeSGetAttributeForEventWihMetaAnyContext",
		Event: map[string]any{
			utils.AccountField: "dan",
			utils.Destination:  "+491511231234",
		},
		APIOpts: map[string]any{
			utils.MetaUsage: 10 * time.Second,
		},
	}
	var attrReply *utils.APIAttributeProfile
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1GetAttributeForEvent,
		ev, &attrReply); err != nil {
		t.Fatal(err)
	}

	expAttrFromEv := &utils.APIAttributeProfile{
		Tenant:    ev.Tenant,
		ID:        "ATTR_2",
		FilterIDs: []string{"*string:~*req.Account:dan", "*prefix:~*req.Destination:+4915", "*exists:~*opts.*usage:", "*notexists:~*req.RequestType:"},
		Attributes: []*utils.ExternalAttribute{
			{
				Path:  utils.MetaReq + utils.NestingSep + utils.AccountField,
				Type:  utils.MetaConstant,
				Value: "1001",
			},
		},
		Weights: utils.DynamicWeights{
			{
				Weight: 20,
			},
		},
	}
	if !reflect.DeepEqual(expAttrFromEv, attrReply) {
		t.Errorf("Expecting: %s, received: %s", utils.ToJSON(expAttrFromEv), utils.ToJSON(attrReply))
	}
}

func testAttributeSGetAttributeForEventNotFound(t *testing.T) {
	ev := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "testAttributeSGetAttributeForEventWihMetaAnyContext",
		Event: map[string]any{
			utils.AccountField: "dann",
			utils.Destination:  "+491511231234",
		},
		APIOpts: map[string]any{
			utils.OptsContext: utils.MetaCDRs,
		},
	}
	var attrReply *utils.APIAttributeProfile
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1GetAttributeForEvent,
		ev, &attrReply); err == nil || err.Error() != utils.ErrNotFound.Error() {
		t.Error(err)
	}
}

func testAttributeSGetAttributeForEvent(t *testing.T) {
	ev := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "testAttributeSGetAttributeForEvent",
		Event: map[string]any{
			utils.AccountField: "1007",
			utils.Destination:  "+491511231234",
		},
		APIOpts: map[string]any{
			utils.MetaUsage:   10 * time.Second,
			utils.OptsContext: utils.MetaSessionS,
		},
	}

	eAttrPrf := &utils.APIAttributeProfile{
		Tenant:    ev.Tenant,
		ID:        "ATTR_1",
		FilterIDs: []string{"*string:~*req.Account:1007", "*string:~*opts.*context:*cdrs|*sessions", "*exists:~*opts.*usage:"},
		Attributes: []*utils.ExternalAttribute{
			{
				Path:      utils.MetaReq + utils.NestingSep + utils.AccountField,
				Value:     "1001",
				Type:      utils.MetaConstant,
				FilterIDs: []string{},
			},
			{
				Path:      utils.MetaReq + utils.NestingSep + utils.Subject,
				Value:     "1001",
				Type:      utils.MetaConstant,
				FilterIDs: []string{},
			},
		},
		Weights: utils.DynamicWeights{
			{
				Weight: 20,
			},
		},
	}
	if *utils.Encoding == utils.MetaGOB {
		eAttrPrf.Attributes[0].FilterIDs = nil
		eAttrPrf.Attributes[1].FilterIDs = nil
	}
	var result string
	eAttrPrfApi := &utils.APIAttributeProfileWithAPIOpts{
		APIAttributeProfile: &utils.APIAttributeProfile{
			Tenant:    ev.Tenant,
			ID:        "ATTR_1",
			FilterIDs: []string{"*string:~*req.Account:1007", "*string:~*opts.*context:*cdrs|*sessions", "*exists:~*opts.*usage:"},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:      utils.MetaReq + utils.NestingSep + utils.AccountField,
					Value:     "1001",
					Type:      utils.MetaConstant,
					FilterIDs: []string{},
				},
				{
					Path:      utils.MetaReq + utils.NestingSep + utils.Subject,
					Value:     "1001",
					Type:      utils.MetaConstant,
					FilterIDs: []string{},
				},
			},
			Weights: utils.DynamicWeights{
				{
					Weight: 20,
				},
			},
		},
	}
	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
		eAttrPrfApi, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}

	var attrReply *utils.APIAttributeProfile
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1GetAttributeForEvent,
		ev, &attrReply); err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(eAttrPrf, attrReply) {
		t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(eAttrPrf), utils.ToJSON(attrReply))
	}

	ev.Tenant = utils.EmptyString
	ev.ID = "randomID"
	var attrPrf *utils.APIAttributeProfile
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1GetAttributeForEvent,
		ev, &attrPrf); err != nil {
		t.Fatal(err)
	}

	// Populate private variables in RSRParsers
	if !reflect.DeepEqual(eAttrPrf, attrPrf) {
		t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(eAttrPrf), utils.ToJSON(attrPrf))
	}
}

func testAttributeSetAttributeProfile(t *testing.T) {
	attrPrf := &utils.APIAttributeProfileWithAPIOpts{
		APIAttributeProfile: &utils.APIAttributeProfile{
			Tenant:    utils.CGRateSorg,
			ID:        "TEST_ATTRIBUTES_IT_TEST",
			FilterIDs: []string{"*string:~*req.Account:1002", "*exists:~*opts.*usage:"},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:  utils.AccountField,
					Type:  utils.MetaConstant,
					Value: "1002",
				},
				{
					Path:  "*tenant",
					Type:  utils.MetaConstant,
					Value: "cgrates.itsyscom",
				},
			},
			Weights: utils.DynamicWeights{
				{
					Weight: 20,
				},
			},
		},
	}
	var reply string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
		attrPrf, &reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Error(err)
	}

	expectedAttr := utils.APIAttributeProfile{
		Tenant:    utils.CGRateSorg,
		ID:        "TEST_ATTRIBUTES_IT_TEST",
		FilterIDs: []string{"*string:~*req.Account:1002", "*exists:~*opts.*usage:"},
		Attributes: []*utils.ExternalAttribute{
			{
				Path:  utils.AccountField,
				Type:  utils.MetaConstant,
				Value: "1002",
			},
			{
				Path:  "*tenant",
				Type:  utils.MetaConstant,
				Value: "cgrates.itsyscom",
			},
		},
		Weights: utils.DynamicWeights{
			{
				Weight: 20,
			},
		},
	}
	var result utils.APIAttributeProfile
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
		&utils.TenantIDWithAPIOpts{
			TenantID: &utils.TenantID{
				Tenant: utils.CGRateSorg,
				ID:     "TEST_ATTRIBUTES_IT_TEST",
			},
		}, &result); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(result, expectedAttr) {
		t.Errorf("Expected %+v \n, received %+v", utils.ToJSON(expectedAttr), utils.ToJSON(result))
	}
}

func testAttributeProcessEvent(t *testing.T) {
	var reply *utils.APIAttributeProfile
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
		utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "ATTR_1"}}, &reply); err != nil {
		t.Fatal(err)
	}
	args := &utils.CGREvent{
		Event: map[string]any{
			utils.ToR:          utils.MetaVoice,
			utils.AccountField: "1002",
		},
		APIOpts: map[string]any{
			utils.MetaUsage:   10 * time.Second,
			utils.OptsContext: utils.MetaCDRs,
		},
	}
	expEvReply := &AttrSProcessEventReply{
		AlteredFields: []*FieldsAltered{
			{
				MatchedProfileID: "cgrates.org:TEST_ATTRIBUTES_IT_TEST",
				Fields:           []string{"*tenant", utils.AccountField},
			},
		},
		CGREvent: &utils.CGREvent{
			Tenant: "cgrates.itsyscom",
			Event: map[string]any{
				utils.AccountField: "1002",
				utils.ToR:          utils.MetaVoice,
			},
			APIOpts: map[string]any{
				utils.MetaUsage:   float64(10 * time.Second),
				utils.OptsContext: utils.MetaCDRs,
			},
		},
	}
	evRply := &AttrSProcessEventReply{}
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
		args, &evRply); err != nil {
		t.Error(err)
	} else {
		sort.Strings(expEvReply.AlteredFields[0].Fields)
		sort.Strings(evRply.AlteredFields[0].Fields)
		if !reflect.DeepEqual(evRply, expEvReply) {
			t.Errorf("Expected %+v, received %+v", expEvReply, evRply)
		}
	}
}

func testAttributeProcessEventWithSearchAndReplace(t *testing.T) {
	attrPrf1 := &utils.APIAttributeProfileWithAPIOpts{
		APIAttributeProfile: &utils.APIAttributeProfile{
			Tenant:    config.CgrConfig().GeneralCfg().DefaultTenant,
			ID:        "ATTR_Search_and_replace",
			FilterIDs: []string{"*string:~*req.Category:call", "*string:~*opts.*context:*sessions", "*exists:~*opts.*usage:"},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:  utils.MetaReq + utils.NestingSep + "Category",
					Value: "~*req.Category:s/(.*)/${1}_suffix/",
				},
			},
			Blockers: utils.DynamicBlockers{
				{
					Blocker: true,
				},
			},
			Weights: utils.DynamicWeights{
				{
					Weight: 20,
				},
			},
		},
	}
	var result string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
		attrPrf1, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	attrArgs := &utils.CGREvent{
		Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
		ID:     "HeaderEventForAttribute",
		Event: map[string]any{
			"Category": "call",
		},
		APIOpts: map[string]any{
			utils.MetaUsage:   10 * time.Second,
			utils.OptsContext: utils.MetaSessionS,
		},
	}
	eRply := &AttrSProcessEventReply{
		AlteredFields: []*FieldsAltered{
			{
				MatchedProfileID: "cgrates.org:ATTR_Search_and_replace",
				Fields:           []string{"*req.Category"},
			},
		},
		CGREvent: &utils.CGREvent{
			Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
			ID:     "HeaderEventForAttribute",
			Event: map[string]any{
				"Category": "call_suffix",
			},
			APIOpts: map[string]any{
				utils.MetaUsage:   float64(10 * time.Second),
				utils.OptsContext: utils.MetaSessionS,
			},
		},
	}
	var rplyEv *AttrSProcessEventReply
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
		attrArgs, &rplyEv); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(eRply, rplyEv) {
		t.Errorf("Expecting: %s, received: %s",
			utils.ToJSON(eRply), utils.ToJSON(rplyEv))
	}
}

func testAttributeSProcessWithMultipleRuns(t *testing.T) {
	attrPrf1 := &utils.APIAttributeProfileWithAPIOpts{
		APIAttributeProfile: &utils.APIAttributeProfile{
			Tenant:    config.CgrConfig().GeneralCfg().DefaultTenant,
			ID:        "ATTR_1",
			FilterIDs: []string{"*string:~*req.InitialField:InitialValue", "*string:~*opts.*context:*sessions", "*exists:~*opts.*usage:"},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:  utils.MetaReq + utils.NestingSep + "Field1",
					Value: "Value1",
				},
			},
			Weights: utils.DynamicWeights{
				{
					Weight: 20,
				},
			},
		},
	}
	attrPrf2 := &utils.APIAttributeProfileWithAPIOpts{
		APIAttributeProfile: &utils.APIAttributeProfile{
			Tenant:    config.CgrConfig().GeneralCfg().DefaultTenant,
			ID:        "ATTR_2",
			FilterIDs: []string{"*string:~*req.Field1:Value1", "*string:~*opts.*context:*sessions", "*exists:~*opts.*usage:"},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:  utils.MetaReq + utils.NestingSep + "Field2",
					Value: "Value2",
				},
			},
			Weights: utils.DynamicWeights{
				{
					Weight: 20,
				},
			},
		},
	}
	attrPrf3 := &utils.APIAttributeProfileWithAPIOpts{
		APIAttributeProfile: &utils.APIAttributeProfile{
			Tenant:    config.CgrConfig().GeneralCfg().DefaultTenant,
			ID:        "ATTR_3",
			FilterIDs: []string{"*string:~*req.NotFound:NotFound", "*string:~*opts.*context:*sessions", "*exists:~*opts.*usage:"},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:  utils.MetaReq + utils.NestingSep + "Field3",
					Value: "Value3",
				},
			},
			Weights: utils.DynamicWeights{
				{
					Weight: 20,
				},
			},
		},
	}
	// Add attribute in DM
	var result string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
		attrPrf1, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
		attrPrf2, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
		attrPrf3, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	attrArgs := &utils.CGREvent{
		Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
		ID:     utils.GenUUID(),
		Event: map[string]any{
			"InitialField": "InitialValue",
		},
		APIOpts: map[string]any{
			utils.MetaUsage:                 10 * time.Second,
			utils.OptsContext:               utils.MetaSessionS,
			utils.OptsAttributesProcessRuns: 4,
		},
	}
	eRply := &AttrSProcessEventReply{
		AlteredFields: []*FieldsAltered{
			{
				MatchedProfileID: "cgrates.org:ATTR_1",
				Fields:           []string{"*req.Field1"},
			},
			{
				MatchedProfileID: "cgrates.org:ATTR_2",
				Fields:           []string{"*req.Field2"},
			},
			{
				MatchedProfileID: "cgrates.org:ATTR_1",
				Fields:           []string{"*req.Field1"},
			},
			{
				MatchedProfileID: "cgrates.org:ATTR_2",
				Fields:           []string{"*req.Field2"},
			},
		},
		CGREvent: &utils.CGREvent{
			Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
			ID:     utils.GenUUID(),
			Event: map[string]any{
				"InitialField": "InitialValue",
				"Field1":       "Value1",
				"Field2":       "Value2",
			},
		},
	}

	var rplyEv AttrSProcessEventReply
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
		attrArgs, &rplyEv); err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(eRply.AlteredFields, rplyEv.AlteredFields) {
		t.Errorf("Expecting %+v, received: %+v", eRply.AlteredFields, rplyEv.AlteredFields)
	} else if !reflect.DeepEqual(eRply.CGREvent.Event, rplyEv.CGREvent.Event) {
		t.Errorf("Expecting %+v, received: %+v", eRply.CGREvent.Event, rplyEv.CGREvent.Event)
	}
}

func testAttributeSProcessWithMultipleRuns2(t *testing.T) {
	attrPrf1 := &utils.APIAttributeProfileWithAPIOpts{
		APIAttributeProfile: &utils.APIAttributeProfile{
			Tenant:    config.CgrConfig().GeneralCfg().DefaultTenant,
			ID:        "ATTR_1",
			FilterIDs: []string{"*string:~*req.InitialField:InitialValue", "*string:~*opts.*context:*sessions", "*exists:~*opts.*usage:"},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:  utils.MetaReq + utils.NestingSep + "Field1",
					Value: "Value1",
				},
			},
			Weights: utils.DynamicWeights{
				{
					Weight: 20,
				},
			},
		},
	}
	attrPrf2 := &utils.APIAttributeProfileWithAPIOpts{
		APIAttributeProfile: &utils.APIAttributeProfile{
			Tenant:    config.CgrConfig().GeneralCfg().DefaultTenant,
			ID:        "ATTR_2",
			FilterIDs: []string{"*string:~*req.Field1:Value1", "*string:~*opts.*context:*sessions", "*exists:~*opts.*usage:"},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:  utils.MetaReq + utils.NestingSep + "Field2",
					Value: "Value2",
				},
			},
			Weights: utils.DynamicWeights{
				{
					Weight: 25,
				},
			},
		},
	}
	attrPrf3 := &utils.APIAttributeProfileWithAPIOpts{
		APIAttributeProfile: &utils.APIAttributeProfile{
			Tenant:    config.CgrConfig().GeneralCfg().DefaultTenant,
			ID:        "ATTR_3",
			FilterIDs: []string{"*string:~*req.Field2:Value2", "*string:~*opts.*context:*sessions", "*exists:~*opts.*usage:"},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:  utils.MetaReq + utils.NestingSep + "Field3",
					Value: "Value3",
				},
			},
			Weights: utils.DynamicWeights{
				{
					Weight: 30,
				},
			},
		},
	}
	// Add attributeProfiles
	var result string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
		attrPrf1, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
		attrPrf2, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
		attrPrf3, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	attrArgs := &utils.CGREvent{
		Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
		ID:     utils.GenUUID(),
		Event: map[string]any{
			"InitialField": "InitialValue",
		},
		APIOpts: map[string]any{
			utils.MetaUsage:                 10 * time.Second,
			utils.OptsContext:               utils.MetaSessionS,
			utils.OptsAttributesProcessRuns: 4,
		},
	}
	eRply := &AttrSProcessEventReply{
		AlteredFields: []*FieldsAltered{
			{
				MatchedProfileID: "cgrates.org:ATTR_1",
				Fields:           []string{"*req.Field1"},
			},
			{
				MatchedProfileID: "cgrates.org:ATTR_2",
				Fields:           []string{"*req.Field2"},
			},
			{
				MatchedProfileID: "cgrates.org:ATTR_2",
				Fields:           []string{"*req.Field2"},
			},
			{
				MatchedProfileID: "cgrates.org:ATTR_3",
				Fields:           []string{"*req.Field3"},
			},
		},
		CGREvent: &utils.CGREvent{
			Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
			ID:     utils.GenUUID(),
			Event: map[string]any{
				"InitialField": "InitialValue",
				"Field1":       "Value1",
				"Field2":       "Value2",
				"Field3":       "Value3",
			},
			APIOpts: map[string]any{
				utils.MetaUsage:                 10 * time.Second,
				utils.OptsContext:               utils.MetaSessionS,
				utils.OptsAttributesProcessRuns: 4,
			},
		},
	}

	var rplyEv AttrSProcessEventReply
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
		attrArgs, &rplyEv); err != nil {
		t.Fatal(err)
	}
	sort.Slice(rplyEv.AlteredFields, func(i, j int) bool {
		return rplyEv.AlteredFields[i].MatchedProfileID < rplyEv.AlteredFields[j].MatchedProfileID
	})
	if !reflect.DeepEqual(eRply.AlteredFields, rplyEv.AlteredFields) {
		t.Errorf("Expecting %+v, received: %+v", utils.ToJSON(eRply.AlteredFields), utils.ToJSON(rplyEv.AlteredFields))
	} else if !reflect.DeepEqual(eRply.CGREvent.Event, rplyEv.CGREvent.Event) {
		t.Errorf("Expecting %+v, received: %+v", eRply.CGREvent.Event, rplyEv.CGREvent.Event)
	}
}

func testAttributeGetAttributeProfileAllIDs(t *testing.T) {
	var rply []string
	expectedIds := []string{"ATTR_1", "ATTR_2", "ATTR_3", "ATTR_Search_and_replace", "TEST_ATTRIBUTES_IT_TEST", "TestA_ATTRIBUTE1",
		"TestA_ATTRIBUTE3", "TestB_ATTRIBUTE1", "TestB_ATTRIBUTE2"} //"TEST_ATTRIBUTES_IT_TEST_SECOND"}
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfileIDs,
		&utils.ArgsItemIDs{
			Tenant: "cgrates.org",
		}, &rply); err != nil {
		t.Error(err)
	} else {
		sort.Strings(rply)
		sort.Strings(expectedIds)
		if !reflect.DeepEqual(expectedIds, rply) {
			t.Errorf("Expected %+v, received %+v", expectedIds, rply)
		}
	}
}

func testAttributeGetAttributeProfileAllCount(t *testing.T) {
	var rply int
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfilesCount,
		&utils.TenantIDWithAPIOpts{
			TenantID: &utils.TenantID{
				Tenant: "cgrates.org",
			},
		}, &rply); err != nil {
		t.Error(err)
	} else if rply != 9 {
		t.Errorf("Expected %+v, received %+v", 9, rply)
	}
}

func testAttributeRemoveRemainAttributeProfiles(t *testing.T) {
	var reply string
	args := &utils.TenantIDWithAPIOpts{
		TenantID: &utils.TenantID{
			ID:     "ATTR_1",
			Tenant: utils.CGRateSorg,
		},
	}
	if err := attrRPC.Call(context.Background(), utils.AdminSv1RemoveAttributeProfile,
		args, &reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Errorf("Expected %+v \n, received %+v", utils.OK, reply)
	}

	args = &utils.TenantIDWithAPIOpts{
		TenantID: &utils.TenantID{
			ID:     "ATTR_2",
			Tenant: utils.CGRateSorg,
		},
	}
	if err := attrRPC.Call(context.Background(), utils.AdminSv1RemoveAttributeProfile,
		args, &reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Errorf("Expected %+v \n, received %+v", utils.OK, reply)
	}

	args = &utils.TenantIDWithAPIOpts{
		TenantID: &utils.TenantID{
			ID:     "ATTR_3",
			Tenant: utils.CGRateSorg,
		},
	}
	if err := attrRPC.Call(context.Background(), utils.AdminSv1RemoveAttributeProfile,
		args, &reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Errorf("Expected %+v \n, received %+v", utils.OK, reply)
	}

	args = &utils.TenantIDWithAPIOpts{
		TenantID: &utils.TenantID{
			ID:     "ATTR_Search_and_replace",
			Tenant: utils.CGRateSorg,
		},
	}
	if err := attrRPC.Call(context.Background(), utils.AdminSv1RemoveAttributeProfile,
		args, &reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Errorf("Expected %+v \n, received %+v", utils.OK, reply)
	}
}

func testAttributeGetAttributeProfileAfterRemove(t *testing.T) {
	var reply *utils.APIAttributeProfile
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
		&utils.TenantIDWithAPIOpts{
			TenantID: &utils.TenantID{
				Tenant: utils.CGRateSorg,
				ID:     "ATTR_Search_and_replace",
			},
		}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
		t.Error(err)
	}

	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
		&utils.TenantIDWithAPIOpts{
			TenantID: &utils.TenantID{
				Tenant: utils.CGRateSorg,
				ID:     "ATTR_1",
			},
		}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
		t.Error(err)
	}

	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
		&utils.TenantIDWithAPIOpts{
			TenantID: &utils.TenantID{
				Tenant: utils.CGRateSorg,
				ID:     "ATTR_2",
			},
		}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
		t.Error(err)
	}

	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetAttributeProfile,
		&utils.TenantIDWithAPIOpts{
			TenantID: &utils.TenantID{
				Tenant: utils.CGRateSorg,
				ID:     "ATTR_3",
			},
		}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
		t.Error(err)
	}
}

func testAttributeSetAttributeProfileWithAttrBlockers(t *testing.T) {
	// the blocker on the profile is false
	attrPrf1 := &utils.APIAttributeProfileWithAPIOpts{
		APIAttributeProfile: &utils.APIAttributeProfile{
			Tenant:    config.CgrConfig().GeneralCfg().DefaultTenant,
			ID:        "ATTR_WITH_BLOCKER_TRUE",
			FilterIDs: []string{"*string:~*req.Blockers:*exists", "*eq:~*opts.*attrProcessRuns:2"},
			Blockers: utils.DynamicBlockers{
				{
					Blocker: false,
				},
			},
			Weights: utils.DynamicWeights{
				{
					Weight: 30,
				},
			},
			Attributes: []*utils.ExternalAttribute{
				{
					Path:  utils.MetaReq + utils.NestingSep + "ToR",
					Value: "*sms",
				},
				{
					Path:  utils.MetaOpts + utils.NestingSep + "*chargers",
					Value: "true",
				},
				{
					Blockers: utils.DynamicBlockers{
						{
							FilterIDs: []string{"*prefix:~*req.Destination:4433"},
							Blocker:   true,
						},
					},
					Path:  utils.MetaReq + utils.NestingSep + "RequestType",
					Value: "*rated",
				},
				{
					Path:  utils.MetaOpts + utils.NestingSep + "*usage",
					Value: "1m",
				},
			},
		},
	}
	// here the the blocker on the profile is true
	attrPrf2 := &utils.APIAttributeProfileWithAPIOpts{
		APIAttributeProfile: &utils.APIAttributeProfile{
			Tenant: config.CgrConfig().GeneralCfg().DefaultTenant,
			ID:     "ATTR_WITH_BLOCKER",
			FilterIDs: []string{"*string:~*req.Blockers:*exists",
				"*notexists:~*opts.*usage:"},
			Blockers: utils.DynamicBlockers{
				{
					Blocker: true,
				},
			},
			Weights: utils.DynamicWeights{
				{
					Weight: 20,
				},
			},
			Attributes: []*utils.ExternalAttribute{
				{
					Blockers: utils.DynamicBlockers{
						{
							FilterIDs: []string{"*prefix:~*req.Destination:4433"},
							Blocker:   true,
						},
					},
					Path:  utils.MetaReq + utils.NestingSep + "Account",
					Value: "10093",
				},
				{
					Path:  utils.MetaOpts + utils.NestingSep + "*rates",
					Value: "true",
				},
			},
		},
	}
	// Add attributeProfiles
	var result string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
		attrPrf1, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
		attrPrf2, &result); err != nil {
		t.Error(err)
	} else if result != utils.OK {
		t.Error("Unexpected reply returned", result)
	}
	// first we will process the second attribute with true BLOCKER on profile, but true on Attributes if the Destination prefix is 4433(FOR NOW THE DESTINATION WILL BE EMPTY)
	args := &utils.CGREvent{
		Tenant: "cgrates.org",
		Event: map[string]any{
			utils.ToR:          utils.MetaVoice,
			utils.AccountField: "1002",
			"Blockers":         "*exists",
		},
		APIOpts: map[string]any{},
	}
	expEvReply := &AttrSProcessEventReply{
		AlteredFields: []*FieldsAltered{
			{
				MatchedProfileID: "cgrates.org:ATTR_WITH_BLOCKER",
				Fields:           []string{"*opts.*rates", "*req.Account"},
			},
		},
		CGREvent: &utils.CGREvent{
			Tenant: "cgrates.org",
			Event: map[string]any{
				utils.ToR:          utils.MetaVoice,
				utils.AccountField: "10093",
				"Blockers":         "*exists",
			},
			APIOpts: map[string]any{
				utils.MetaRates: "true",
			},
		},
	}
	evRply := &AttrSProcessEventReply{}
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
		args, &evRply); err != nil {
		t.Error(err)
	} else {
		sort.Strings(expEvReply.AlteredFields[0].Fields)
		sort.Strings(evRply.AlteredFields[0].Fields)
		if !reflect.DeepEqual(evRply, expEvReply) {
			t.Errorf("Expected %+v, received %+v", utils.ToJSON(expEvReply), utils.ToJSON(evRply))
		}
	}
}

func testAttributeSetAttributeProfileWithAttrBlockers2(t *testing.T) {
	// first we will process the second attribute with true BLOCKER on profile, but true on Attributes if the Destination prefix is 4433(NOW WE WILL POPULATE THE DESTINATION, AND THE BLOCKER WILL STOP THE NEXT ATTRIBUTES)
	args := &utils.CGREvent{
		Tenant: "cgrates.org",
		Event: map[string]any{
			utils.ToR:          utils.MetaVoice,
			utils.AccountField: "1002",
			"Blockers":         "*exists",
			utils.Destination:  "4433254",
		},
		APIOpts: map[string]any{},
	}
	expEvReply := &AttrSProcessEventReply{
		AlteredFields: []*FieldsAltered{
			{
				MatchedProfileID: "cgrates.org:ATTR_WITH_BLOCKER",
				Fields:           []string{"*req.Account"},
			},
		},
		CGREvent: &utils.CGREvent{
			Tenant: "cgrates.org",
			Event: map[string]any{
				utils.ToR:          utils.MetaVoice,
				utils.AccountField: "10093",
				"Blockers":         "*exists",
				utils.Destination:  "4433254",
			},
			// now *rates was not processde ebcause the blocker amtched the filter of Destination
			APIOpts: map[string]any{},
		},
	}
	evRply := &AttrSProcessEventReply{}
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
		args, &evRply); err != nil {
		t.Error(err)
	} else {
		sort.Strings(expEvReply.AlteredFields[0].Fields)
		sort.Strings(evRply.AlteredFields[0].Fields)
		if !reflect.DeepEqual(evRply, expEvReply) {
			t.Errorf("Expected %+v, received %+v", utils.ToJSON(expEvReply), utils.ToJSON(evRply))
		}
	}
}

func testAttributeSetAttributeProfileBlockersBothProfilesProcessRuns(t *testing.T) {
	// now we will process both attributes that matched, but blokcers on the attributes will be felt and not all attributes from the list were processed
	args := &utils.CGREvent{
		Tenant: "cgrates.org",
		Event: map[string]any{
			utils.ToR:          utils.MetaVoice,
			utils.AccountField: "1002",
			"Blockers":         "*exists",
			utils.Destination:  "4433254",
		},
		APIOpts: map[string]any{
			utils.OptsAttributesProcessRuns: 2,
		},
	}
	expEvReply := &AttrSProcessEventReply{
		AlteredFields: []*FieldsAltered{
			{
				MatchedProfileID: "cgrates.org:ATTR_WITH_BLOCKER_TRUE",
				Fields:           []string{"*opts.*chargers", "*req.RequestType", "*req.ToR"},
			},
			{
				MatchedProfileID: "cgrates.org:ATTR_WITH_BLOCKER",
				Fields:           []string{"*req.Account"},
			},
		},
		CGREvent: &utils.CGREvent{
			Tenant: "cgrates.org",
			Event: map[string]any{
				utils.ToR:          "*sms",
				utils.AccountField: "10093",
				"Blockers":         "*exists",
				utils.Destination:  "4433254",
				utils.RequestType:  "*rated",
			},
			// now *rates was not processde ebcause the blocker amtched the filter of Destination
			APIOpts: map[string]any{
				utils.OptsAttributesProcessRuns: 2.,
				utils.MetaChargers:              "true",
			},
		},
	}
	evRply := &AttrSProcessEventReply{}
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
		args, &evRply); err != nil {
		t.Error(err)
	} else {
		sort.Strings(expEvReply.AlteredFields[0].Fields)
		sort.Strings(evRply.AlteredFields[0].Fields)
		if !reflect.DeepEqual(evRply, expEvReply) {
			t.Errorf("Expected %+v, received %+v", utils.ToJSON(expEvReply), utils.ToJSON(evRply))
		}
	}
}

func testAttributeSSetNonIndexedTypeFilter(t *testing.T) {
	filter := &engine.FilterWithAPIOpts{
		Filter: &engine.Filter{
			Tenant: "cgrates.org",
			ID:     "NONINDEXED_FLTR_TYPE",
			Rules: []*engine.FilterRule{
				{
					Type:    utils.MetaGreaterThan,
					Element: utils.Cost,
					Values:  []string{"10"},
				},
			},
		},
	}
	var reply string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetFilter, filter,
		&reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Error("Unexpected reply returned")
	}

	args := &utils.CGREvent{
		Event: map[string]any{
			utils.ToR:          utils.MetaVoice,
			utils.AccountField: "1002",
			"Blockers":         "*exists",
			utils.Destination:  "44322",
		},
		APIOpts: map[string]any{},
	}
	expEvReply := &AttrSProcessEventReply{
		AlteredFields: []*FieldsAltered{
			{
				MatchedProfileID: "cgrates.org:ATTR_WITH_BLOCKER",
				Fields:           []string{"*opts.*rates", "*req.Account"},
			},
		},
		CGREvent: &utils.CGREvent{
			Tenant: "cgrates.org",
			Event: map[string]any{
				utils.ToR:          utils.MetaVoice,
				utils.AccountField: "10093",
				"Blockers":         "*exists",
				utils.Destination:  "44322",
			},
			APIOpts: map[string]any{
				utils.MetaRates: "true",
			},
		},
	}
	evRply := &AttrSProcessEventReply{}
	if err := attrRPC.Call(context.Background(), utils.AttributeSv1ProcessEvent,
		args, &evRply); err != nil {
		t.Error(err)
	} else {
		sort.Strings(expEvReply.AlteredFields[0].Fields)
		sort.Strings(evRply.AlteredFields[0].Fields)
		if !reflect.DeepEqual(evRply, expEvReply) {
			t.Errorf("Expected %+v, received %+v", utils.ToJSON(expEvReply), utils.ToJSON(evRply))
		}
	}
}

func testAttributeSSetIndexedTypeFilter(t *testing.T) {
	filter := &engine.FilterWithAPIOpts{
		Filter: &engine.Filter{
			Tenant: "cgrates.org",
			ID:     "INDEXED_FLTR_TYPE",
			Rules: []*engine.FilterRule{
				{
					Type:    utils.MetaPrefix,
					Element: "~*req.Account",
					Values:  []string{"10"},
				},
			},
		},
	}
	var reply string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetFilter, filter,
		&reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Error("Unexpected reply: ", reply)
	}
}

func testAttributeSClearIndexes(t *testing.T) {
	// args := &apis.AttrRemFilterIndexes{
	// 	Tenant:   "cgrates.org",
	// 	ItemType: utils.MetaAttributes,
	// }

	// using a map instead because the object causes import cycle.
	args := map[string]string{
		"Tenant":   "cgrates.org",
		"ItemType": utils.MetaAttributes,
	}

	var reply string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1RemoveFilterIndexes,
		args, &reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Error("Unexpected reply: ", reply)
	}
}

func testAttributeSCheckIndexesSetAttributeProfileWithoutFilters(t *testing.T) {
	attrPrf := &utils.APIAttributeProfileWithAPIOpts{
		APIAttributeProfile: &utils.APIAttributeProfile{
			Tenant: "cgrates.org",
			ID:     "ATTR_TEST",
			Attributes: []*utils.ExternalAttribute{
				{
					Type:  utils.MetaConstant,
					Path:  "~*req.Account",
					Value: "1002",
				},
			},
		},
	}

	var reply string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
		attrPrf, &reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Error(err)
	}

	expIdx := []string{"*none:*any:*any:ATTR_TEST"}

	// args := &apis.AttrGetFilterIndexes{
	// 	Tenant:   "cgrates.org",
	// 	ItemType: utils.MetaAttributes,
	// }

	// using a map instead because the object causes import cycle.
	args := map[string]string{
		"Tenant":   "cgrates.org",
		"ItemType": utils.MetaAttributes,
	}
	var replyIdx []string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetFilterIndexes,
		args, &replyIdx); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(replyIdx, expIdx) {
		t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ToJSON(expIdx), utils.ToJSON(replyIdx))
	}
}

// func testAttributeSCheckIndexesAddNonIndexedFilter(t *testing.T) {
// 	attrPrf := &utils.APIAttributeProfileWithAPIOpts{
// 		APIAttributeProfile: &utils.APIAttributeProfile{
// 			Tenant:    "cgrates.org",
// 			ID:        "ATTR_TEST",
// 			FilterIDs: []string{"NONINDEXED_FLTR_TYPE"},
// 			Attributes: []*utils.ExternalAttribute{
// 				{
// 					Type:  utils.MetaConstant,
// 					Path:  "~*req.Account",
// 					Value: "1002",
// 				},
// 			},
// 		},
// 	}

// 	var reply string
// 	if err := attrSRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
// 		attrPrf, &reply); err != nil {
// 		t.Error(err)
// 	} else if reply != utils.OK {
// 		t.Error(err)
// 	}

// 	expIdx := []string{"*none:*any:*any:ATTR_TEST"}
// 	args := &AttrGetFilterIndexes{
// 		Tenant:   "cgrates.org",
// 		ItemType: utils.MetaAttributes,
// 	}
// 	var replyIdx []string
// 	if err := attrSRPC.Call(context.Background(), utils.AdminSv1GetFilterIndexes,
// 		args, &replyIdx); err != nil {
// 		t.Error(err)
// 	} else if !reflect.DeepEqual(replyIdx, expIdx) {
// 		t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ToJSON(expIdx), utils.ToJSON(replyIdx))
// 	}
// }

func testAttributeSCheckIndexesAddIndexedFilters(t *testing.T) {
	attrPrf := &utils.APIAttributeProfileWithAPIOpts{
		APIAttributeProfile: &utils.APIAttributeProfile{
			Tenant:    "cgrates.org",
			ID:        "ATTR_TEST",
			FilterIDs: []string{"NONINDEXED_FLTR_TYPE", "INDEXED_FLTR_TYPE"},
			Attributes: []*utils.ExternalAttribute{
				{
					Type:  utils.MetaConstant,
					Path:  "~*req.Account",
					Value: "1002",
				},
			},
		},
	}

	var reply string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
		attrPrf, &reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Error(err)
	}

	expIdx := []string{"*prefix:*req.Account:10:ATTR_TEST"}

	// args := &apis.AttrGetFilterIndexes{
	// 	Tenant:   "cgrates.org",
	// 	ItemType: utils.MetaAttributes,
	// }

	// using a map instead because the object causes import cycle.
	args := map[string]string{
		"Tenant":   "cgrates.org",
		"ItemType": utils.MetaAttributes,
	}
	var replyIdx []string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetFilterIndexes,
		args, &replyIdx); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(replyIdx, expIdx) {
		t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ToJSON(expIdx), utils.ToJSON(replyIdx))
	}

	attrPrf = &utils.APIAttributeProfileWithAPIOpts{
		APIAttributeProfile: &utils.APIAttributeProfile{
			Tenant:    "cgrates.org",
			ID:        "ATTR_TEST",
			FilterIDs: []string{"NONINDEXED_FLTR_TYPE", "INDEXED_FLTR_TYPE", "*string:~*req.Category:call"},
			Attributes: []*utils.ExternalAttribute{
				{
					Type:  utils.MetaConstant,
					Path:  "~*req.Account",
					Value: "1002",
				},
			},
		},
	}

	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
		attrPrf, &reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Error(err)
	}

	expIdx = []string{"*prefix:*req.Account:10:ATTR_TEST", "*string:*req.Category:call:ATTR_TEST"}
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetFilterIndexes,
		args, &replyIdx); err != nil {
		t.Error(err)
	} else {
		sort.Strings(replyIdx)
		if !reflect.DeepEqual(replyIdx, expIdx) {
			t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ToJSON(expIdx), utils.ToJSON(replyIdx))
		}
	}
}

func testAttributeSCheckIndexesModifyIndexedFilter(t *testing.T) {
	filter := &engine.FilterWithAPIOpts{
		Filter: &engine.Filter{
			Tenant: "cgrates.org",
			ID:     "INDEXED_FLTR_TYPE",
			Rules: []*engine.FilterRule{
				{
					Type:    utils.MetaSuffix,
					Element: "~*req.Subject",
					Values:  []string{"01"},
				},
			},
		},
	}
	var reply string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetFilter, filter,
		&reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Error("Unexpected reply: ", reply)
	}

	expIdx := []string{"*string:*req.Category:call:ATTR_TEST", "*suffix:*req.Subject:01:ATTR_TEST"}

	// args := &apis.AttrGetFilterIndexes{
	// 	Tenant:   "cgrates.org",
	// 	ItemType: utils.MetaAttributes,
	// }

	// using a map instead because the object causes import cycle.
	args := map[string]string{
		"Tenant":   "cgrates.org",
		"ItemType": utils.MetaAttributes,
	}
	var replyIdx []string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetFilterIndexes,
		args, &replyIdx); err != nil {
		t.Error(err)
	} else {
		sort.Strings(replyIdx)
		if !reflect.DeepEqual(replyIdx, expIdx) {
			t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ToJSON(expIdx), utils.ToJSON(replyIdx))
		}
	}
}

func testAttributeSCheckIndexesRemoveAnIndexedFilter(t *testing.T) {
	attrPrf := &utils.APIAttributeProfileWithAPIOpts{
		APIAttributeProfile: &utils.APIAttributeProfile{
			Tenant:    "cgrates.org",
			ID:        "ATTR_TEST",
			FilterIDs: []string{"NONINDEXED_FLTR_TYPE", "INDEXED_FLTR_TYPE"},
			Attributes: []*utils.ExternalAttribute{
				{
					Type:  utils.MetaConstant,
					Path:  "~*req.Account",
					Value: "1002",
				},
			},
		},
	}

	var reply string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1SetAttributeProfile,
		attrPrf, &reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Error(err)
	}

	expIdx := []string{"*suffix:*req.Subject:01:ATTR_TEST"}

	// args := &apis.AttrGetFilterIndexes{
	// 	Tenant:   "cgrates.org",
	// 	ItemType: utils.MetaAttributes,
	// }

	// using a map instead because the object causes import cycle.
	args := map[string]string{
		"Tenant":   "cgrates.org",
		"ItemType": utils.MetaAttributes,
	}
	var replyIdx []string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetFilterIndexes,
		args, &replyIdx); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(replyIdx, expIdx) {
		t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ToJSON(expIdx), utils.ToJSON(replyIdx))
	}
}
func testAttributeSCheckIndexesRemoveAttributeProfile(t *testing.T) {
	argsRem := &utils.TenantIDWithAPIOpts{
		TenantID: &utils.TenantID{
			Tenant: "cgrates.org",
			ID:     "ATTR_TEST",
		},
	}
	var reply string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1RemoveAttributeProfile,
		argsRem, &reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Error(err)
	}

	// args := &apis.AttrGetFilterIndexes{
	// 	Tenant:   "cgrates.org",
	// 	ItemType: utils.MetaAttributes,
	// }

	// using a map instead because the object causes import cycle.
	args := map[string]string{
		"Tenant":   "cgrates.org",
		"ItemType": utils.MetaAttributes,
	}

	var replyIdx []string
	if err := attrRPC.Call(context.Background(), utils.AdminSv1GetFilterIndexes,
		args, &replyIdx); err == nil || err.Error() != utils.ErrNotFound.Error() {
		t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ErrNotFound, err)
	}
}

// Kill the engine when it is about to be finished
func testAttributesKillEngine(t *testing.T) {
	if err := engine.KillEngine(100); err != nil {
		t.Error(err)
	}
}

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

	content := `{
"attributes": {
	"enabled": true
},
"db": {
	"db_conns": {
		"*default": {
			"db_type": "*internal",
			"opts":{
			"internalDBRewriteInterval": "0s",
			"internalDBDumpInterval": "0s"
		}
    	}
	},
},
"admins": {
	"enabled": true
}
}`

	tpFiles := map[string]string{
		utils.AttributesCsv: `#Tenant,ID,FilterIDs,Weights,Blockers,AttributeFilterIDs,AttributeBlockers,Path,Type,Value
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`,
	}

	testEnv := engine.TestEngine{
		ConfigJSON: content,
		TpFiles:    tpFiles,
		DBCfg:      dbCfg,
		Encoding:   *utils.Encoding,
	}
	client, _ := testEnv.Run(t)

	t.Run("SetAttributesThroughAPI", func(t *testing.T) {
		var reply string
		attrPrf := &utils.APIAttributeProfileWithAPIOpts{
			APIAttributeProfile: &utils.APIAttributeProfile{
				Tenant:    "cgrates.org",
				ID:        "ATTR_API",
				FilterIDs: []string{"*string:~*req.AttrSource:api"},
				Attributes: []*utils.ExternalAttribute{
					{
						Path:  "*req.3*4",
						Type:  utils.MetaMultiply,
						Value: "3;4",
					},
					{
						Path:  "*req.12/4",
						Type:  utils.MetaDivide,
						Value: "12;4",
					},
					{
						Path:  "*req.3+4",
						Type:  utils.MetaSum,
						Value: "3;4",
					},
					{
						Path:  "*req.3-4",
						Type:  utils.MetaDifference,
						Value: "3;4",
					},
					{
						Path:  "*req.MultiplyBetweenVariables",
						Type:  utils.MetaMultiply,
						Value: "~*req.Elem1;~*req.Elem2",
					},
				},
			},
		}
		if err := client.Call(context.Background(), utils.AdminSv1SetAttributeProfile, 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": "api",
				"Elem1":      "3",
				"Elem2":      "4",
			},
		}
		expected := AttrSProcessEventReply{
			AlteredFields: []*FieldsAltered{
				{
					MatchedProfileID: "cgrates.org:ATTR_API",
					Fields:           []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":               "api",
					"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 AttrSProcessEventReply
		if err := client.Call(context.Background(), utils.AttributeSv1ProcessEvent,
			ev, &reply); err != nil {
			t.Error(err)
		} else {
			sort.Strings(expected.AlteredFields[0].Fields)
			sort.Strings(reply.AlteredFields[0].Fields)
			if !reflect.DeepEqual(expected, reply) {
				t.Errorf("\nexpected: %s, \nreceived: %s",
					utils.ToJSON(expected), utils.ToJSON(reply))
			}
		}

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

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