//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 (
	"os"
	"path"
	"reflect"
	"sort"
	"testing"
	"time"

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

var (
	ldrCtxCfgPath string
	ldrCtxCfg     *config.CGRConfig
	ldrCtxRPC     *birpc.Client
	ldrCtxConfDIR string //run tests for specific configuration
	ldrCtxDelay   int

	sTestsLdrCtx = []func(t *testing.T){
		testLoaderNoContextRemoveFolders,
		testLoaderNoContextCreateFolders,

		testLoaderNoContextLoadConfig,
		testLoaderNoContextInitDataDb,
		testLoaderNoContextResetStorDb,
		testLoaderNoContextStartEngine,
		testLoaderNoContextRpcConn,

		testLoaderNoContextWriteCSVs,
		testLoaderNoContextLoadTariffPlans,
		testLoaderNoContextGetFilterIndexesAfterLoad,
		testLoaderNoContextSetProfiles,
		testLoaderNoContextGetFilterIndexesAfterSet,

		testLoaderNoContextStopEngine,
		testLoaderNoContextRemoveFolders,
	}
)

func TestLoaderNoContextIT(t *testing.T) {
	switch *utils.DBType {
	case utils.MetaInternal:
		ldrCtxConfDIR = "tutinternal"
	case utils.MetaMySQL:
		ldrCtxConfDIR = "tutmysql"
	case utils.MetaMongo:
		ldrCtxConfDIR = "tutmongo"
	case utils.MetaPostgres:
		t.SkipNow()
	default:
		t.Fatal("Unknown Database type")
	}

	for _, stest := range sTestsLdrCtx {
		t.Run(ldrCtxConfDIR, stest)
	}
}

func testLoaderNoContextLoadConfig(t *testing.T) {
	var err error
	ldrCtxCfgPath = path.Join(*utils.DataDir, "conf", "samples", ldrCtxConfDIR)
	if ldrCtxCfg, err = config.NewCGRConfigFromPath(ldrCtxCfgPath); err != nil {
		t.Error(err)
	}
	ldrCtxDelay = 1000
}

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

func testLoaderNoContextResetStorDb(t *testing.T) {
	if err := engine.InitStorDb(ldrCtxCfg); err != nil {
		t.Fatal(err)
	}
}

func testLoaderNoContextStartEngine(t *testing.T) {
	if _, err := engine.StopStartEngine(ldrCtxCfgPath, ldrCtxDelay); err != nil {
		t.Fatal(err)
	}
}

func testLoaderNoContextRpcConn(t *testing.T) {
	ldrCtxRPC = engine.NewRPCClient(t, ldrCtxCfg.ListenCfg())
}

func testLoaderNoContextWriteCSVs(t *testing.T) {
	writeFile := func(fileName, data string) error {
		csvFile, err := os.Create(path.Join("/tmp/TestLoaderNoContextIT", fileName))
		if err != nil {
			return err
		}
		defer csvFile.Close()
		_, err = csvFile.WriteString(data)
		if err != nil {
			return err

		}
		return csvFile.Sync()
	}

	// Create and populate Attributes.csv
	if err := writeFile(utils.AttributesCsv, `
#Tenant,ID,Contexts,FilterIDs,ActivationInterval,AttributeFilterIDs,Path,Type,Value,Blocker,Weight
cgrates.org,ATTR_1,,*string:~*req.Field1:Value1,,,*req.Field1,*constant,Value2,false,10
cgrates.org,ATTR_2,,,,,*req.Field2,*constant,Value2,false,10
`); err != nil {
		t.Fatal(err)
	}

	// Create and populate DispatcherProfiles.csv
	if err := writeFile(utils.DispatcherProfilesCsv, `
#Tenant,ID,Subsystems,FilterIDs,ActivationInterval,Strategy,StrategyParameters,ConnID,ConnFilterIDs,ConnWeight,ConnBlocker,ConnParameters,Weight
cgrates.org,DSP1,,,,*weight,,ALL,,20,false,,10
cgrates.org,DSP1,,,,,,ALL2,,10,,,
cgrates.org,DSP2,,*string:~*req.Field1:Value1,,*weight,,connID,,20,false,,20
cgrates.org,DSP2,,,,,,ALL2,,10,,,
`); err != nil {
		t.Fatal(err)
	}
}

func testLoaderNoContextLoadTariffPlans(t *testing.T) {
	var reply string
	attrs := &utils.AttrLoadTpFromFolder{FolderPath: "/tmp/TestLoaderNoContextIT"}
	if err := ldrCtxRPC.Call(context.Background(), utils.APIerSv1LoadTariffPlanFromFolder, attrs, &reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Error("Unexpected reply returned", reply)
	}
	time.Sleep(500 * time.Millisecond)
}

func testLoaderNoContextGetFilterIndexesAfterLoad(t *testing.T) {
	// check attribute profile filter indexes
	expIdx := []string{
		"*none:*any:*any:ATTR_2",
		"*string:*req.Field1:Value1:ATTR_1",
	}
	var result []string
	if err := ldrCtxRPC.Call(context.Background(), utils.APIerSv1GetFilterIndexes, &v1.AttrGetFilterIndexes{
		ItemType: utils.MetaAttributes,
		Tenant:   "cgrates.org",
		Context:  utils.MetaAny,
	}, &result); err != nil {
		t.Error(err)
	} else {
		sort.Strings(result)
		if !reflect.DeepEqual(expIdx, result) {
			t.Errorf("expected: %+v,\nreceived: %+v", expIdx, result)
		}
	}

	// check dispatcher profile filter indexes
	expIdx = []string{
		"*none:*any:*any:DSP1",
		"*string:*req.Field1:Value1:DSP2",
	}
	if err := ldrCtxRPC.Call(context.Background(), utils.APIerSv1GetFilterIndexes, &v1.AttrGetFilterIndexes{
		ItemType: utils.MetaDispatchers,
		Tenant:   "cgrates.org",
		Context:  utils.MetaAny,
	}, &result); err != nil {
		t.Error(err)
	} else {
		sort.Strings(result)
		if !reflect.DeepEqual(expIdx, result) {
			t.Errorf("expected: %+v,\nreceived: %+v", expIdx, result)
		}
	}
}

func testLoaderNoContextSetProfiles(t *testing.T) {
	// set attribute profile
	attrPrf := &engine.AttributeProfileWithAPIOpts{
		AttributeProfile: &engine.AttributeProfile{
			Tenant: "cgrates.org",
			ID:     "ATTR_3",
			// Contexts:  []string{utils.MetaAny},
			FilterIDs: []string{"*string:~*req.Field3:Value3"},
			Attributes: []*engine.Attribute{
				{
					Path:  utils.MetaReq + utils.NestingSep + "Field3",
					Type:  utils.MetaConstant,
					Value: config.NewRSRParsersMustCompile("Value4", utils.InfieldSep),
				},
			},
			Weight: 20,
		},
	}
	attrPrf.Compile()
	var reply string
	if err := ldrCtxRPC.Call(context.Background(), utils.APIerSv1SetAttributeProfile, attrPrf, &reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Error("Unexpected reply returned", reply)
	}
	var attrReply *engine.AttributeProfile
	if err := ldrCtxRPC.Call(context.Background(), utils.APIerSv1GetAttributeProfile,
		utils.TenantIDWithAPIOpts{TenantID: &utils.TenantID{Tenant: "cgrates.org", ID: "ATTR_3"}}, &attrReply); err != nil {
		t.Error(err)
	} else {
		attrReply.Compile()
		attrPrf.AttributeProfile.Contexts = []string{utils.MetaAny}
		if !reflect.DeepEqual(attrPrf.AttributeProfile, attrReply) {
			t.Errorf("expected : %+v,\nreceived: %+v",
				utils.ToJSON(attrPrf.AttributeProfile), utils.ToJSON(attrReply))
		}
	}

	// set dispatcher profile
	dspPrf := &v1.DispatcherWithAPIOpts{
		DispatcherProfile: &engine.DispatcherProfile{
			Tenant: "cgrates.org",
			// Subsystems: []string{utils.MetaAny},
			ID:        "DSP3",
			FilterIDs: []string{"*string:~*req.RandomField:RandomValue"},
			Strategy:  utils.MetaFirst,
			Weight:    20,
		},
	}

	if err := ldrCtxRPC.Call(context.Background(), utils.APIerSv1SetDispatcherProfile,
		dspPrf,
		&reply); err != nil {
		t.Error(err)
	} else if reply != utils.OK {
		t.Error("Unexpected reply returned", reply)
	}

	var dspReply *engine.DispatcherProfile
	if err := ldrCtxRPC.Call(context.Background(), utils.APIerSv1GetDispatcherProfile,
		&utils.TenantID{Tenant: "cgrates.org", ID: "DSP3"},
		&dspReply); err != nil {
		t.Error(err)
	} else {
		dspPrf.DispatcherProfile.Subsystems = []string{utils.MetaAny}
		if !reflect.DeepEqual(dspPrf.DispatcherProfile, dspReply) {
			t.Errorf("expected: %+v,\nreceived: %+v", utils.ToJSON(dspPrf.DispatcherProfile), utils.ToJSON(dspReply))
		}
	}
}

func testLoaderNoContextGetFilterIndexesAfterSet(t *testing.T) {
	// check attribute profile filter indexes
	expIdx := []string{
		"*none:*any:*any:ATTR_2",
		"*string:*req.Field1:Value1:ATTR_1",
		"*string:*req.Field3:Value3:ATTR_3",
	}
	var result []string
	if err := ldrCtxRPC.Call(context.Background(), utils.APIerSv1GetFilterIndexes, &v1.AttrGetFilterIndexes{
		ItemType: utils.MetaAttributes,
		Tenant:   "cgrates.org",
		Context:  utils.MetaAny,
	}, &result); err != nil {
		t.Error(err)
	} else {
		sort.Strings(result)
		if !reflect.DeepEqual(expIdx, result) {
			t.Errorf("expected: %+v,\nreceived: %+v", expIdx, result)
		}
	}

	// check dispatcher profile filter indexes
	expIdx = []string{
		"*none:*any:*any:DSP1",
		"*string:*req.Field1:Value1:DSP2",
		"*string:*req.RandomField:RandomValue:DSP3",
	}
	if err := ldrCtxRPC.Call(context.Background(), utils.APIerSv1GetFilterIndexes, &v1.AttrGetFilterIndexes{
		ItemType: utils.MetaDispatchers,
		Tenant:   "cgrates.org",
		Context:  utils.MetaAny,
	}, &result); err != nil {
		t.Error(err)
	} else {
		sort.Strings(result)
		if !reflect.DeepEqual(expIdx, result) {
			t.Errorf("expected: %+v,\nreceived: %+v", expIdx, result)
		}
	}
}

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

func testLoaderNoContextCreateFolders(t *testing.T) {
	if err := os.MkdirAll("/tmp/TestLoaderNoContextIT", 0755); err != nil {
		t.Error(err)
	}
}

func testLoaderNoContextRemoveFolders(t *testing.T) {
	if err := os.RemoveAll("/tmp/TestLoaderNoContextIT"); err != nil {
		t.Error(err)
	}
}
