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

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

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

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

import (
	"bytes"
	"encoding/json"
	"log"
	"os"
	"reflect"
	"strings"
	"sync"
	"testing"
	"time"

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

var (
	marsh = NewCodecMsgpackMarshaler()
)

func init() {
	dm.DataDB().Flush("")
	populateDB()
}

func populateDB() {
	ats := []*Action{
		{ActionType: "*topup",
			Balance: &BalanceFilter{Type: utils.StringPointer(utils.MetaMonetary),
				Value: &utils.ValueFormula{Static: 10}}},
		{ActionType: "*topup",
			Balance: &BalanceFilter{Type: utils.StringPointer(utils.MetaVoice),
				Weight:         utils.Float64Pointer(20),
				Value:          &utils.ValueFormula{Static: 10 * float64(time.Second)},
				DestinationIDs: utils.StringMapPointer(utils.NewStringMap("NAT"))}},
	}

	ats1 := []*Action{
		{ActionType: "*topup",
			Balance: &BalanceFilter{
				Type:  utils.StringPointer(utils.MetaMonetary),
				Value: &utils.ValueFormula{Static: 10}}, Weight: 10},
		{ActionType: "*reset_account", Weight: 20},
	}

	minu := &Account{
		ID: "vdf:minu",
		BalanceMap: map[string]Balances{
			utils.MetaMonetary: {&Balance{Value: 50}},
			utils.MetaVoice: {
				&Balance{Value: 200 * float64(time.Second),
					DestinationIDs: utils.NewStringMap("NAT"), Weight: 10},
				&Balance{Value: 100 * float64(time.Second),
					DestinationIDs: utils.NewStringMap("RET"), Weight: 20},
			}},
	}
	broker := &Account{
		ID: "vdf:broker",
		BalanceMap: map[string]Balances{
			utils.MetaVoice: {
				&Balance{Value: 20 * float64(time.Second),
					DestinationIDs: utils.NewStringMap("NAT"),
					Weight:         10, RatingSubject: "rif"},
				&Balance{Value: 100 * float64(time.Second),
					DestinationIDs: utils.NewStringMap("RET"), Weight: 20},
			}},
	}
	luna := &Account{
		ID: "vdf:luna",
		BalanceMap: map[string]Balances{
			utils.MetaMonetary: {
				&Balance{Value: 0, Weight: 20},
			}},
	}
	// this is added to test if csv load tests account will not overwrite balances
	minitsboy := &Account{
		ID: "vdf:minitsboy",
		BalanceMap: map[string]Balances{
			utils.MetaVoice: {
				&Balance{Value: 20 * float64(time.Second),
					DestinationIDs: utils.NewStringMap("NAT"),
					Weight:         10, RatingSubject: "rif"},
				&Balance{Value: 100 * float64(time.Second),
					DestinationIDs: utils.NewStringMap("RET"), Weight: 20},
			},
			utils.MetaMonetary: {
				&Balance{Value: 100, Weight: 10},
			},
		},
	}
	max := &Account{
		ID: "cgrates.org:max",
		BalanceMap: map[string]Balances{
			utils.MetaMonetary: {
				&Balance{Value: 11, Weight: 20},
			}},
	}
	money := &Account{
		ID: "cgrates.org:money",
		BalanceMap: map[string]Balances{
			utils.MetaMonetary: {
				&Balance{Value: 10000, Weight: 10},
			}},
	}
	if dm.dataDB != nil {
		dm.SetActions("TEST_ACTIONS", ats)
		dm.SetActions("TEST_ACTIONS_ORDER", ats1)
		dm.SetAccount(broker)
		dm.SetAccount(minu)
		dm.SetAccount(minitsboy)
		dm.SetAccount(luna)
		dm.SetAccount(max)
		dm.SetAccount(money)
	} else {
		log.Fatal("Could not connect to db!")
	}
}

func debitTest(t *testing.T, wg *sync.WaitGroup) {
	t1 := time.Date(2017, time.February, 2, 17, 30, 0, 0, time.UTC)
	t2 := time.Date(2017, time.February, 2, 17, 30, 59, 0, time.UTC)
	cd := &CallDescriptor{Category: "call",
		Tenant:  "cgrates.org",
		Account: "moneyp", Subject: "nt",
		Destination: "49", TimeStart: t1,
		TimeEnd: t2, LoopIndex: 0}
	if _, err := cd.Debit(nil); err != nil {
		t.Errorf("Error debiting balance: %s", err)
	}
	wg.Done()
}

func TestSerialDebit(t *testing.T) {
	var wg sync.WaitGroup
	initialBalance := 10000.0
	moneyConcurent := &Account{
		ID: "cgrates.org:moneyp",
		BalanceMap: map[string]Balances{
			utils.MetaMonetary: {
				&Balance{Value: initialBalance, Weight: 10},
			}},
	}
	if err := dm.SetAccount(moneyConcurent); err != nil {
		t.Error(err)
	}
	debitsToDo := 50
	for i := 0; i < debitsToDo; i++ {
		wg.Add(1)
		debitTest(t, &wg)
	}
	wg.Wait()
	t1 := time.Date(2017, time.February, 2, 17, 30, 0, 0, time.UTC)
	t2 := time.Date(2017, time.February, 2, 17, 30, 59, 0, time.UTC)
	cd := &CallDescriptor{Category: "call",
		Tenant: "cgrates.org", Account: "moneyp", Subject: "nt",
		Destination: "49", TimeStart: t1, TimeEnd: t2, LoopIndex: 0}

	acc, err := cd.getAccount()
	if err != nil {
		t.Errorf("Error debiting balance: %+v", err)
	}
	expBalance := initialBalance - float64(debitsToDo*60)
	if acc.BalanceMap[utils.MetaMonetary][0].GetValue() != expBalance {
		t.Errorf("Balance does not match: %f, expected %f",
			acc.BalanceMap[utils.MetaMonetary][0].GetValue(), expBalance)
	}

}

func TestParallelDebit(t *testing.T) {
	var wg sync.WaitGroup
	initialBalance := 10000.0
	moneyConcurent := &Account{
		ID: "cgrates.org:moneyp",
		BalanceMap: map[string]Balances{
			utils.MetaMonetary: {
				&Balance{Value: initialBalance, Weight: 10},
			}},
	}
	if err := dm.SetAccount(moneyConcurent); err != nil {
		t.Error(err)
	}
	debitsToDo := 50
	for i := 0; i < debitsToDo; i++ {
		wg.Add(1)
		go debitTest(t, &wg)
	}
	wg.Wait()
	time.Sleep(10 * time.Millisecond)
	t1 := time.Date(2017, time.February, 2, 17, 30, 0, 0, time.UTC)
	t2 := time.Date(2017, time.February, 2, 17, 30, 59, 0, time.UTC)
	cd := &CallDescriptor{
		Category: "call", Tenant: "cgrates.org",
		Account: "moneyp", Subject: "nt", Destination: "49",
		TimeStart: t1, TimeEnd: t2, LoopIndex: 0}

	acc, err := cd.getAccount()
	if err != nil {
		t.Errorf("Error debiting balance: %+v", err)
	}
	expBalance := initialBalance - float64(debitsToDo*60)
	if acc.BalanceMap[utils.MetaMonetary][0].GetValue() != expBalance {
		t.Errorf("Balance does not match: %f, expected %f",
			acc.BalanceMap[utils.MetaMonetary][0].GetValue(), expBalance)
	}

}

func TestSplitSpans(t *testing.T) {
	t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC)
	t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "vdf", Subject: "rif",
		Destination: "0256", TimeStart: t1, TimeEnd: t2, ToR: utils.MetaVoice}

	cd.LoadRatingPlans()
	timespans := cd.splitInTimeSpans()
	if len(timespans) != 2 {
		t.Log(cd.RatingInfos)
		t.Error("Wrong number of timespans: ", len(timespans))
	}
}

func TestSplitSpansWeekend(t *testing.T) {
	cd := &CallDescriptor{
		Category:        "postpaid",
		ToR:             utils.MetaVoice,
		Tenant:          "foehn",
		Subject:         "foehn",
		Account:         "foehn",
		Destination:     "0034678096720",
		TimeStart:       time.Date(2015, 4, 24, 7, 59, 4, 0, time.UTC),
		TimeEnd:         time.Date(2015, 4, 24, 8, 2, 0, 0, time.UTC),
		LoopIndex:       0,
		DurationIndex:   176 * time.Second,
		FallbackSubject: "",
		RatingInfos: RatingInfos{
			&RatingInfo{
				MatchedSubject: "*out:foehn:postpaid:foehn",
				MatchedPrefix:  "0034678",
				MatchedDestId:  "SPN_MOB",
				ActivationTime: time.Date(2015, 4, 23, 0, 0, 0, 0, time.UTC),
				RateIntervals: []*RateInterval{
					{
						Timing: &RITiming{
							WeekDays:  []time.Weekday{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday},
							StartTime: "08:00:00",
						},
						Rating: &RIRate{
							ConnectFee:       0,
							RoundingMethod:   "*up",
							RoundingDecimals: 6,
							Rates: RateGroups{
								&RGRate{Value: 1, RateIncrement: time.Second, RateUnit: time.Second},
							},
						},
					},
					{
						Timing: &RITiming{
							WeekDays:  []time.Weekday{time.Monday, time.Tuesday, time.Wednesday, time.Thursday, time.Friday},
							StartTime: "00:00:00",
						},
						Rating: &RIRate{
							ConnectFee:       0,
							RoundingMethod:   "*up",
							RoundingDecimals: 6,
							Rates: RateGroups{
								&RGRate{Value: 1, RateIncrement: time.Second, RateUnit: time.Second},
							},
						},
					},
					{
						Timing: &RITiming{
							WeekDays:  []time.Weekday{time.Saturday, time.Sunday},
							StartTime: "00:00:00",
						},
						Rating: &RIRate{
							ConnectFee:       0,
							RoundingMethod:   "*up",
							RoundingDecimals: 6,
							Rates: RateGroups{
								&RGRate{Value: 1, RateIncrement: time.Second, RateUnit: time.Second},
							},
						},
					},
				},
			},
		},
	}

	timespans := cd.splitInTimeSpans()
	if len(timespans) != 2 {
		t.Log(cd.RatingInfos)
		t.Error("Wrong number of timespans: ", len(timespans))
	}
	if timespans[0].RateInterval == nil ||
		timespans[0].RateInterval.Timing.StartTime != "00:00:00" ||
		timespans[1].RateInterval == nil ||
		timespans[1].RateInterval.Timing.StartTime != "08:00:00" {
		t.Errorf("Error setting rateinterval: %+v %+v", timespans[0].RateInterval.Timing.StartTime, timespans[1].RateInterval.Timing.StartTime)
	}
}

func TestSplitSpansRoundToIncrements(t *testing.T) {
	t1 := time.Date(2013, time.October, 7, 14, 50, 0, 0, time.UTC)
	t2 := time.Date(2013, time.October, 7, 14, 52, 12, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "test", Subject: "trp",
		Destination: "0256", TimeStart: t1, TimeEnd: t2, DurationIndex: 132 * time.Second}

	cd.LoadRatingPlans()
	timespans := cd.splitInTimeSpans()
	if len(timespans) != 2 {
		t.Logf("%+v", cd)
		t.Log(cd.RatingInfos)
		t.Error("Wrong number of timespans: ", len(timespans))
	}
	var d time.Duration
	for _, ts := range timespans {
		d += ts.GetDuration()
	}
	if d != 132*time.Second {
		t.Error("Wrong duration for timespans: ", d)
	}
}

func TestCalldescHolliday(t *testing.T) {
	cd := &CallDescriptor{
		TimeStart: time.Date(2015, time.May, 1, 13, 30, 0, 0, time.UTC),
		TimeEnd:   time.Date(2015, time.May, 1, 13, 35, 26, 0, time.UTC),
		RatingInfos: RatingInfos{
			&RatingInfo{
				RateIntervals: RateIntervalList{
					&RateInterval{
						Timing: &RITiming{WeekDays: utils.WeekDays{1, 2, 3, 4, 5}, StartTime: "00:00:00"},
						Weight: 10,
					},
					&RateInterval{
						Timing: &RITiming{WeekDays: utils.WeekDays{6, 7}, StartTime: "00:00:00"},
						Weight: 10,
					},
					&RateInterval{
						Timing: &RITiming{Months: utils.Months{time.May}, MonthDays: utils.MonthDays{1}, StartTime: "00:00:00"},
						Weight: 20,
					},
				},
			},
		},
	}
	timespans := cd.splitInTimeSpans()
	if len(timespans) != 1 {
		t.Error("Error assiging holidy rate interval: ", timespans)
	}
	if timespans[0].RateInterval.Timing.MonthDays == nil {
		t.Errorf("Error setting holiday rate interval: %+v", timespans[0].RateInterval.Timing)
	}
}

func TestGetCost(t *testing.T) {
	t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC)
	t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "vdf", Subject: "rif",
		Destination: "0256", TimeStart: t1, TimeEnd: t2, LoopIndex: 0}
	result, _ := cd.GetCost()
	expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 2701}
	if result.Cost != expected.Cost || result.GetConnectFee() != 1 {
		t.Errorf("Expected %v was %v", expected, result)
	}
}

func TestGetCostRounding(t *testing.T) {
	t1 := time.Date(2017, time.February, 2, 17, 30, 0, 0, time.UTC)
	t2 := time.Date(2017, time.February, 2, 17, 33, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "call", Tenant: "cgrates.org",
		Subject: "round", Destination: "49",
		TimeStart: t1, TimeEnd: t2, LoopIndex: 0}
	result, _ := cd.GetCost()
	if result.Cost != 0.3001 || result.GetConnectFee() != 0 { // should be 0.3 :(
		t.Error("bad cost", utils.ToIJSON(result))
	}
}

func TestDebitRounding(t *testing.T) {
	t1 := time.Date(2017, time.February, 2, 17, 30, 0, 0, time.UTC)
	t2 := time.Date(2017, time.February, 2, 17, 33, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "call", Tenant: "cgrates.org",
		Subject: "round", Destination: "49",
		TimeStart: t1, TimeEnd: t2, LoopIndex: 0}
	result, _ := cd.Debit(nil)
	if result.Cost != 0.30006 || result.GetConnectFee() != 0 { // should be 0.3 :(
		t.Error("bad cost", utils.ToJSON(result))
	}
}

func TestDebitPerformRounding(t *testing.T) {
	t1 := time.Date(2017, time.February, 2, 17, 30, 0, 0, time.UTC)
	t2 := time.Date(2017, time.February, 2, 17, 33, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "call",
		Tenant: "cgrates.org", Subject: "round", Destination: "49",
		TimeStart: t1, TimeEnd: t2, LoopIndex: 0, PerformRounding: true}
	if result, err := cd.Debit(nil); err != nil {
		t.Error(err)
	} else if result.Cost != 0.3001 || result.GetConnectFee() != 0 { // should be 0.3 :(
		t.Error("bad cost", utils.ToIJSON(result))
	}
}

func TestGetCostZero(t *testing.T) {
	t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC)
	t2 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "vdf", Subject: "rif",
		Destination: "0256", TimeStart: t1, TimeEnd: t2, LoopIndex: 0}
	result, _ := cd.GetCost()
	expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 0}
	if result.Cost != expected.Cost || result.GetConnectFee() != 0 {
		t.Errorf("Expected %v was %v", expected, result)
	}
}

func TestGetCostTimespans(t *testing.T) {
	t1 := time.Date(2013, time.October, 8, 9, 23, 2, 0, time.UTC)
	t2 := time.Date(2013, time.October, 8, 9, 24, 27, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "test", Subject: "trp",
		Destination: "0256", TimeStart: t1, TimeEnd: t2,
		LoopIndex: 0, DurationIndex: 85 * time.Second}
	result, _ := cd.GetCost()
	expected := &CallCost{Tenant: "test", Subject: "trp", Destination: "0256", Cost: 85}
	if result.Cost != expected.Cost || result.GetConnectFee() != 0 || len(result.Timespans) != 2 {
		t.Errorf("Expected %+v was %+v", expected, result)
	}

}

func TestGetCostRatingPlansAndRatingIntervals(t *testing.T) {
	t1 := time.Date(2012, time.February, 27, 23, 50, 0, 0, time.UTC)
	t2 := time.Date(2012, time.February, 28, 18, 10, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "CUSTOMER_1",
		Subject: "rif:from:tm", Destination: "49178", TimeStart: t1, TimeEnd: t2,
		LoopIndex: 0, DurationIndex: t2.Sub(t1)}
	result, _ := cd.GetCost()
	if len(result.Timespans) != 3 ||
		!result.Timespans[0].TimeEnd.Equal(result.Timespans[1].TimeStart) ||
		!result.Timespans[1].TimeEnd.Equal(result.Timespans[2].TimeStart) {
		for _, ts := range result.Timespans {
			t.Logf("TS %+v", ts)
		}
		t.Errorf("Expected %+v was %+v", 3, len(result.Timespans))
	}
}

func TestGetCostRatingPlansAndRatingIntervalsMore(t *testing.T) {
	t1 := time.Date(2012, time.February, 27, 9, 50, 0, 0, time.UTC)
	t2 := time.Date(2012, time.February, 28, 18, 10, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "CUSTOMER_1", Subject: "rif:from:tm",
		Destination: "49178", TimeStart: t1, TimeEnd: t2, LoopIndex: 0, DurationIndex: t2.Sub(t1)}
	result, _ := cd.GetCost()
	if len(result.Timespans) != 4 ||
		!result.Timespans[0].TimeEnd.Equal(result.Timespans[1].TimeStart) ||
		!result.Timespans[1].TimeEnd.Equal(result.Timespans[2].TimeStart) ||
		!result.Timespans[2].TimeEnd.Equal(result.Timespans[3].TimeStart) {
		for _, ts := range result.Timespans {
			t.Logf("TS %+v", ts)
		}
		t.Errorf("Expected %+v was %+v", 4, len(result.Timespans))
	}
}

func TestGetCostRatingPlansAndRatingIntervalsMoreDays(t *testing.T) {
	t1 := time.Date(2012, time.February, 20, 9, 50, 0, 0, time.UTC)
	t2 := time.Date(2012, time.February, 23, 18, 10, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "CUSTOMER_1", Subject: "rif:from:tm",
		Destination: "49178", TimeStart: t1, TimeEnd: t2, LoopIndex: 0, DurationIndex: t2.Sub(t1)}
	result, _ := cd.GetCost()
	if len(result.Timespans) != 8 ||
		!result.Timespans[0].TimeEnd.Equal(result.Timespans[1].TimeStart) ||
		!result.Timespans[1].TimeEnd.Equal(result.Timespans[2].TimeStart) ||
		!result.Timespans[2].TimeEnd.Equal(result.Timespans[3].TimeStart) ||
		!result.Timespans[3].TimeEnd.Equal(result.Timespans[4].TimeStart) ||
		!result.Timespans[4].TimeEnd.Equal(result.Timespans[5].TimeStart) ||
		!result.Timespans[5].TimeEnd.Equal(result.Timespans[6].TimeStart) ||
		!result.Timespans[6].TimeEnd.Equal(result.Timespans[7].TimeStart) {
		for _, ts := range result.Timespans {
			t.Logf("TS %+v", ts)
		}
		t.Errorf("Expected %+v was %+v", 4, len(result.Timespans))
	}
}

func TestGetCostRatingPlansAndRatingIntervalsMoreDaysWeekend(t *testing.T) {
	t1 := time.Date(2012, time.February, 24, 9, 50, 0, 0, time.UTC)
	t2 := time.Date(2012, time.February, 27, 18, 10, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "CUSTOMER_1", Subject: "rif:from:tm",
		Destination: "49178", TimeStart: t1, TimeEnd: t2, LoopIndex: 0, DurationIndex: t2.Sub(t1)}
	result, _ := cd.GetCost()
	if len(result.Timespans) != 5 ||
		!result.Timespans[0].TimeEnd.Equal(result.Timespans[1].TimeStart) ||
		!result.Timespans[1].TimeEnd.Equal(result.Timespans[2].TimeStart) ||
		!result.Timespans[2].TimeEnd.Equal(result.Timespans[3].TimeStart) ||
		!result.Timespans[3].TimeEnd.Equal(result.Timespans[4].TimeStart) {
		for _, ts := range result.Timespans {
			t.Logf("TS %+v", ts)
		}
		t.Errorf("Expected %+v was %+v", 4, len(result.Timespans))
	}
}

func TestGetCostRateGroups(t *testing.T) {
	t1 := time.Date(2013, time.October, 7, 14, 50, 0, 0, time.UTC)
	t2 := time.Date(2013, time.October, 7, 14, 52, 12, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "test", Subject: "trp",
		Destination: "0256", TimeStart: t1, TimeEnd: t2, DurationIndex: 132 * time.Second}

	result, err := cd.GetCost()
	if err != nil {
		t.Error("Error getting cost: ", err)
	}
	if result.Cost != 132 {
		t.Error("Error calculating cost: ", result.Timespans)
	}
}

func TestGetCostNoConnectFee(t *testing.T) {
	t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC)
	t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "vdf", Subject: "rif",
		Destination: "0256", TimeStart: t1, TimeEnd: t2, LoopIndex: 1}
	result, _ := cd.GetCost()
	expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 2700}
	// connect fee is not added because LoopIndex is 1
	if result.Cost != expected.Cost || result.GetConnectFee() != 1 {
		t.Errorf("Expected %v was %v", expected, result)
	}
}

func TestGetCostAccount(t *testing.T) {
	t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC)
	t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "vdf", Subject: "rif",
		Account: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
	result, _ := cd.GetCost()
	expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 2701}
	if result.Cost != expected.Cost || result.GetConnectFee() != 1 {
		t.Errorf("Expected %v was %v", expected, result)
	}
}

func TestFullDestNotFound(t *testing.T) {
	t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC)
	t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "vdf", Subject: "rif",
		Destination: "0256308200", TimeStart: t1, TimeEnd: t2}
	result, _ := cd.GetCost()
	expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 2701}
	if result.Cost != expected.Cost || result.GetConnectFee() != 1 {
		t.Log(cd.RatingInfos)
		t.Errorf("Expected %v was %v", expected, result)
	}
}

func TestSubjectNotFound(t *testing.T) {
	t1 := time.Date(2013, time.February, 1, 17, 30, 0, 0, time.UTC)
	t2 := time.Date(2013, time.February, 1, 18, 30, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "vdf", Subject: "not_exiting",
		Destination: "025740532", TimeStart: t1, TimeEnd: t2}
	result, _ := cd.GetCost()
	expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0257", Cost: 2701}
	if result.Cost != expected.Cost || result.GetConnectFee() != 1 {
		//t.Logf("%+v", result.Timespans[0].RateInterval)
		t.Errorf("Expected %v was %v", expected, result)
	}
}

func TestSubjectNotFoundCostNegativeOne(t *testing.T) {
	t1 := time.Date(2013, time.February, 1, 17, 30, 0, 0, time.UTC)
	t2 := time.Date(2013, time.February, 1, 18, 30, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "cgrates.org", Subject: "not_exiting",
		Destination: "025740532", TimeStart: t1, TimeEnd: t2}
	result, _ := cd.GetCost()
	if result.Cost != -1 || result.GetConnectFee() != 0 {
		//t.Logf("%+v", result.Timespans[0].RateInterval)
		t.Errorf("Expected -1 was %v", result)
	}
}

func TestMultipleRatingPlans(t *testing.T) {
	t1 := time.Date(2012, time.February, 8, 17, 30, 0, 0, time.UTC)
	t2 := time.Date(2012, time.February, 8, 18, 30, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "vdf", Subject: "rif",
		Destination: "0257308200", TimeStart: t1, TimeEnd: t2}
	result, _ := cd.GetCost()
	expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0257", Cost: 2701}
	if result.Cost != expected.Cost || result.GetConnectFee() != 1 {
		t.Log(result.Timespans)
		t.Errorf("Expected %v was %v", expected, result)
	}
}

func TestSpansMultipleRatingPlans(t *testing.T) {
	t1 := time.Date(2012, time.February, 7, 23, 50, 0, 0, time.UTC)
	t2 := time.Date(2012, time.February, 8, 0, 30, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "vdf", Subject: "rif",
		Destination: "0257308200", TimeStart: t1, TimeEnd: t2}
	cc, _ := cd.GetCost()
	if cc.Cost != 2100 || cc.GetConnectFee() != 0 {
		t.Errorf("Expected %v was %v (%v)", 2100, cc, cc.GetConnectFee())
	}
}

func TestLessThanAMinute(t *testing.T) {
	t1 := time.Date(2012, time.February, 8, 23, 50, 0, 0, time.UTC)
	t2 := time.Date(2012, time.February, 8, 23, 50, 30, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "vdf", Subject: "rif",
		Destination: "0257308200", TimeStart: t1, TimeEnd: t2}
	result, _ := cd.GetCost()
	expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0257", Cost: 15}
	if result.Cost != expected.Cost || result.GetConnectFee() != 0 {
		t.Errorf("Expected %v was %v", expected, result)
	}
}

func TestUniquePrice(t *testing.T) {
	t1 := time.Date(2012, time.February, 8, 22, 50, 0, 0, time.UTC)
	t2 := time.Date(2012, time.February, 8, 23, 50, 21, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "vdf", Subject: "rif",
		Destination: "0723045326", TimeStart: t1, TimeEnd: t2}
	result, _ := cd.GetCost()
	expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0723", Cost: 1810.5}
	if result.Cost != expected.Cost || result.GetConnectFee() != 0 {
		t.Errorf("Expected %v was %v", expected, result)
	}
}

func TestMinutesCost(t *testing.T) {
	t1 := time.Date(2012, time.February, 8, 22, 50, 0, 0, time.UTC)
	t2 := time.Date(2012, time.February, 8, 22, 51, 50, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "vdf", Subject: "rif",
		Destination: "0723", TimeStart: t1, TimeEnd: t2}
	result, _ := cd.GetCost()
	expected := &CallCost{Tenant: "vdf", Subject: "minutosu", Destination: "0723", Cost: 55}
	if result.Cost != expected.Cost || result.GetConnectFee() != 0 {
		t.Errorf("Expected %v was %v", expected, result)
	}
}

func TestMaxSessionTimeNoAccount(t *testing.T) {
	cd := &CallDescriptor{
		TimeStart:   time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC),
		TimeEnd:     time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC),
		Category:    "0",
		Tenant:      "vdf",
		Subject:     "ttttttt",
		Destination: "0723"}
	result, err := cd.GetMaxSessionDuration(nil)
	if result != 0 || err == nil {
		t.Errorf("Expected %v was %v (%v)", 0, result, err)
	}
}

func TestMaxSessionTimeWithAccount(t *testing.T) {
	cd := &CallDescriptor{
		TimeStart:   time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC),
		TimeEnd:     time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC),
		Category:    "0",
		Tenant:      "vdf",
		Subject:     "minu",
		Destination: "0723",
	}
	result, err := cd.GetMaxSessionDuration(nil)
	expected := time.Minute
	if result != expected || err != nil {
		t.Errorf("Expected %v was %v", expected, result)
	}
}

func TestMaxSessionTimeWithMaxRate(t *testing.T) {
	ap, err := dm.GetActionPlan("TOPUP10_AT", true, true, utils.NonTransactional)
	if err != nil {
		t.FailNow()
	}
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"cgrates.org:12345": true, "cgrates.org:123456": true}
		at.Execute(nil, "")
	}
	cd := &CallDescriptor{
		Category:    "call",
		Tenant:      "cgrates.org",
		Subject:     "12345",
		Account:     "12345",
		Destination: "447956",
		TimeStart:   time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC),
		TimeEnd:     time.Date(2014, 3, 4, 6, 1, 0, 0, time.UTC),
		MaxRate:     1.0,
		MaxRateUnit: time.Minute,
	}
	result, err := cd.GetMaxSessionDuration(nil)
	expected := 40 * time.Second
	if result != expected || err != nil {
		t.Errorf("Expected %v was %v", expected, result)
	}
}

func TestMaxSessionTimeWithMaxCost(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP10_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"cgrates.org:max": true}
		at.Execute(nil, "")
	}
	cd := &CallDescriptor{
		Category:     "call",
		Tenant:       "cgrates.org",
		Subject:      "max",
		Account:      "max",
		Destination:  "0723123113",
		TimeStart:    time.Date(2015, 3, 23, 6, 0, 0, 0, time.UTC),
		TimeEnd:      time.Date(2015, 3, 23, 6, 30, 0, 0, time.UTC),
		MaxCostSoFar: 0,
	}
	result, err := cd.GetMaxSessionDuration(nil)
	expected := 10 * time.Second
	if result != expected || err != nil {
		t.Errorf("Expected %v was %v", expected, result)
	}
}

func TestGetMaxSessiontWithBlocker(t *testing.T) {
	ap, _ := dm.GetActionPlan("BLOCK_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"cgrates.org:block": true}
		at.Execute(nil, "")
	}
	acc, err := dm.GetAccount("cgrates.org:block")
	if err != nil {
		t.Error("error getting account: ", err)
	}
	if len(acc.BalanceMap[utils.MetaMonetary]) != 2 ||
		acc.BalanceMap[utils.MetaMonetary][0].Blocker != true {
		for _, b := range acc.BalanceMap[utils.MetaMonetary] {
			t.Logf("B: %+v", b)
		}
		t.Error("Error executing action  plan on account: ", acc.BalanceMap[utils.MetaMonetary])
	}
	cd := &CallDescriptor{
		Category:     "call",
		Tenant:       "cgrates.org",
		Subject:      "block",
		Account:      "block",
		Destination:  "0723",
		TimeStart:    time.Date(2016, 1, 13, 14, 0, 0, 0, time.UTC),
		TimeEnd:      time.Date(2016, 1, 13, 14, 30, 0, 0, time.UTC),
		MaxCostSoFar: 0,
	}

	result, err := cd.GetMaxSessionDuration(nil)
	if err != nil {
		t.Error(err)
	} else if result != 17*time.Minute {
		t.Errorf("expected <%+v>, \nreceived <%+v>", 17*time.Minute, result)
	}
	cd = &CallDescriptor{
		Category:     "call",
		Tenant:       "cgrates.org",
		Subject:      "block",
		Account:      "block",
		Destination:  "444",
		TimeStart:    time.Date(2016, 1, 13, 14, 0, 0, 0, time.UTC),
		TimeEnd:      time.Date(2016, 1, 13, 14, 30, 0, 0, time.UTC),
		MaxCostSoFar: 0,
	}
	result, err = cd.GetMaxSessionDuration(nil)
	expected := 30 * time.Minute
	if result != expected || err != nil {
		t.Errorf("Expected %v was %v (%v)", expected, result, err)
	}
}

func TestGetMaxSessiontWithBlockerEmpty(t *testing.T) {
	ap, _ := dm.GetActionPlan("BLOCK_EMPTY_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"cgrates.org:block_empty": true}
		at.Execute(nil, "")
	}
	acc, err := dm.GetAccount("cgrates.org:block_empty")
	if err != nil {
		t.Error("error getting account: ", err)
	}
	if len(acc.BalanceMap[utils.MetaMonetary]) != 2 ||
		acc.BalanceMap[utils.MetaMonetary][0].Blocker != true {
		for _, b := range acc.BalanceMap[utils.MetaMonetary] {
			t.Logf("B: %+v", b)
		}
		t.Error("Error executing action  plan on account: ", acc.BalanceMap[utils.MetaMonetary])
	}
	cd := &CallDescriptor{
		Category:     "call",
		Tenant:       "cgrates.org",
		Subject:      "block",
		Account:      "block_empty",
		Destination:  "0723",
		TimeStart:    time.Date(2016, 1, 13, 14, 0, 0, 0, time.UTC),
		TimeEnd:      time.Date(2016, 1, 13, 14, 30, 0, 0, time.UTC),
		MaxCostSoFar: 0,
	}
	result, err := cd.GetMaxSessionDuration(nil)
	if err != nil {
		t.Error(err)
	} else if result != 0*time.Minute {
		t.Errorf("expected <%+v>, \nreceived <%+v>", 0*time.Minute, result)
	}
	cd = &CallDescriptor{
		Category:     "call",
		Tenant:       "cgrates.org",
		Subject:      "block",
		Account:      "block_empty",
		Destination:  "444",
		TimeStart:    time.Date(2016, 1, 13, 14, 0, 0, 0, time.UTC),
		TimeEnd:      time.Date(2016, 1, 13, 14, 30, 0, 0, time.UTC),
		MaxCostSoFar: 0,
	}
	result, err = cd.GetMaxSessionDuration(nil)
	expected := 30 * time.Minute
	if result != expected || err != nil {
		t.Errorf("Expected %v was %v (%v)", expected, result, err)
	}
}

func TestGetCostWithMaxCost(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP10_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"cgrates.org:max": true}
		at.Execute(nil, "")
	}
	cd := &CallDescriptor{
		Category:     "call",
		Tenant:       "cgrates.org",
		Subject:      "max",
		Account:      "max",
		Destination:  "0723123113",
		TimeStart:    time.Date(2015, 3, 23, 6, 0, 0, 0, time.UTC),
		TimeEnd:      time.Date(2015, 3, 23, 6, 30, 0, 0, time.UTC),
		MaxCostSoFar: 0,
	}
	cc, err := cd.GetCost()
	expected := 1800.0
	if cc.Cost != expected || err != nil {
		t.Errorf("Expected %v was %v", expected, cc.Cost)
	}
}

func TestGetCostRoundingIssue(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP10_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"cgrates.org:dy": true}
		at.Execute(nil, "")
	}
	cd := &CallDescriptor{
		Category:     "call",
		Tenant:       "cgrates.org",
		Subject:      "dy",
		Account:      "dy",
		Destination:  "0723123113",
		TimeStart:    time.Date(2015, 10, 26, 13, 29, 27, 0, time.UTC),
		TimeEnd:      time.Date(2015, 10, 26, 13, 29, 51, 0, time.UTC),
		MaxCostSoFar: 0,
	}
	cc, err := cd.GetCost()
	expected := 0.17
	if cc.Cost != expected || err != nil {
		t.Log(utils.ToIJSON(cc))
		t.Errorf("Expected %v was %+v", expected, cc)
	}
}

func TestGetCostRatingInfoOnZeroTime(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP10_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"cgrates.org:dy": true}
		at.Execute(nil, "")
	}
	cd := &CallDescriptor{
		Category:     "call",
		Tenant:       "cgrates.org",
		Subject:      "dy",
		Account:      "dy",
		Destination:  "0723123113",
		TimeStart:    time.Date(2015, 10, 26, 13, 29, 27, 0, time.UTC),
		TimeEnd:      time.Date(2015, 10, 26, 13, 29, 27, 0, time.UTC),
		MaxCostSoFar: 0,
	}
	cc, err := cd.GetCost()
	if err != nil ||
		len(cc.Timespans) != 1 ||
		cc.Timespans[0].MatchedDestId != "RET" ||
		cc.Timespans[0].MatchedSubject != "*out:cgrates.org:call:dy" ||
		cc.Timespans[0].MatchedPrefix != "0723" ||
		cc.Timespans[0].RatingPlanId != "DY_PLAN" {
		t.Error("MatchedInfo not added:", utils.ToIJSON(cc))
	}
}

func TestDebitRatingInfoOnZeroTime(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP10_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"cgrates.org:dy": true}
		at.Execute(nil, "")
	}
	cd := &CallDescriptor{
		Category:     "call",
		Tenant:       "cgrates.org",
		Subject:      "dy",
		Account:      "dy",
		Destination:  "0723123113",
		TimeStart:    time.Date(2015, 10, 26, 13, 29, 27, 0, time.UTC),
		TimeEnd:      time.Date(2015, 10, 26, 13, 29, 27, 0, time.UTC),
		MaxCostSoFar: 0,
	}
	cc, err := cd.Debit(nil)
	if err != nil ||
		cc == nil ||
		len(cc.Timespans) != 1 ||
		cc.Timespans[0].MatchedDestId != "RET" ||
		cc.Timespans[0].MatchedSubject != "*out:cgrates.org:call:dy" ||
		cc.Timespans[0].MatchedPrefix != "0723" ||
		cc.Timespans[0].RatingPlanId != "DY_PLAN" {
		t.Error("MatchedInfo not added:", utils.ToIJSON(cc))
	}
}

func TestMaxDebitRatingInfoOnZeroTime(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP10_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"cgrates.org:dy": true}
		at.Execute(nil, "")
	}
	cd := &CallDescriptor{
		Category:     "call",
		Tenant:       "cgrates.org",
		Subject:      "dy",
		Account:      "dy",
		Destination:  "0723123113",
		TimeStart:    time.Date(2015, 10, 26, 13, 29, 27, 0, time.UTC),
		TimeEnd:      time.Date(2015, 10, 26, 13, 29, 27, 0, time.UTC),
		MaxCostSoFar: 0,
	}
	cc, err := cd.MaxDebit(nil)
	if err != nil ||
		len(cc.Timespans) != 1 ||
		cc.Timespans[0].MatchedDestId != "RET" ||
		cc.Timespans[0].MatchedSubject != "*out:cgrates.org:call:dy" ||
		cc.Timespans[0].MatchedPrefix != "0723" ||
		cc.Timespans[0].RatingPlanId != "DY_PLAN" {
		t.Error("MatchedInfo not added:", utils.ToIJSON(cc))
	}
}

func TestMaxDebitUnknowDest(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP10_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"cgrates.org:dy": true}
		at.Execute(nil, "")
	}
	cd := &CallDescriptor{
		Category:     "call",
		Tenant:       "cgrates.org",
		Subject:      "dy",
		Account:      "dy",
		Destination:  "9999999999",
		TimeStart:    time.Date(2015, 10, 26, 13, 29, 27, 0, time.UTC),
		TimeEnd:      time.Date(2015, 10, 26, 13, 29, 29, 0, time.UTC),
		MaxCostSoFar: 0,
	}
	cc, err := cd.MaxDebit(nil)
	if err == nil || err != utils.ErrUnauthorizedDestination {
		t.Errorf("Bad error reported %+v: %v", cc, err)
	}
}

func TestMaxDebitRoundingIssue(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP10_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"cgrates.org:dy": true}
		at.Execute(nil, "")
	}
	cd := &CallDescriptor{
		Category:        "call",
		Tenant:          "cgrates.org",
		Subject:         "dy",
		Account:         "dy",
		Destination:     "0723123113",
		TimeStart:       time.Date(2015, 10, 26, 13, 29, 27, 0, time.UTC),
		TimeEnd:         time.Date(2015, 10, 26, 13, 29, 51, 0, time.UTC),
		MaxCostSoFar:    0,
		PerformRounding: true,
	}
	acc, err := dm.GetAccount("cgrates.org:dy")
	if err != nil || acc.BalanceMap[utils.MetaMonetary][0].Value != 1 {
		t.Errorf("Error getting account: %+v (%v)", utils.ToIJSON(acc), err)
	}

	cc, err := cd.MaxDebit(nil)
	expected := 0.17
	if cc.Cost != expected || err != nil {
		t.Log(utils.ToIJSON(cc))
		t.Errorf("Expected %v was %+v (%v)", expected, cc, err)
	}
	acc, err = dm.GetAccount("cgrates.org:dy")
	if err != nil || acc.BalanceMap[utils.MetaMonetary][0].Value != 1-expected {
		t.Errorf("Error getting account: %+v (%v)", utils.ToIJSON(acc), err)
	}
}

func TestDebitRoundingRefund(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP10_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"cgrates.org:dy": true}
		at.Execute(nil, "")
	}
	cd := &CallDescriptor{
		Category:        "call",
		Tenant:          "cgrates.org",
		Subject:         "dy",
		Account:         "dy",
		Destination:     "0723123113",
		TimeStart:       time.Date(2016, 3, 4, 13, 50, 00, 0, time.UTC),
		TimeEnd:         time.Date(2016, 3, 4, 13, 53, 00, 0, time.UTC),
		MaxCostSoFar:    0,
		PerformRounding: true,
	}
	acc, err := dm.GetAccount("cgrates.org:dy")
	if err != nil || acc.BalanceMap[utils.MetaMonetary][0].Value != 1 {
		t.Errorf("Error getting account: %+v (%v)", utils.ToIJSON(acc), err)
	}

	cc, err := cd.Debit(nil)
	expected := 0.3
	if cc.Cost != expected || err != nil {
		t.Log(utils.ToIJSON(cc))
		t.Errorf("Expected %v was %+v (%v)", expected, cc, err)
	}
	acc, err = dm.GetAccount("cgrates.org:dy")
	if err != nil || acc.BalanceMap[utils.MetaMonetary][0].Value != 1-expected {
		t.Errorf("Error getting account: %+v (%v)", utils.ToIJSON(acc), err)
	}
}

func TestMaxSessionTimeWithMaxCostFree(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP10_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"cgrates.org:max": true}
		at.Execute(nil, "")
	}
	cd := &CallDescriptor{
		Category:     "call",
		Tenant:       "cgrates.org",
		Subject:      "max",
		Account:      "max",
		Destination:  "0723123113",
		TimeStart:    time.Date(2015, 3, 23, 19, 0, 0, 0, time.UTC),
		TimeEnd:      time.Date(2015, 3, 23, 19, 30, 0, 0, time.UTC),
		MaxCostSoFar: 0,
	}
	result, err := cd.GetMaxSessionDuration(nil)
	expected := 30 * time.Minute
	if result != expected || err != nil {
		t.Errorf("Expected %v was %v", expected, result)
	}
}

func TestMaxDebitWithMaxCostFree(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP10_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"cgrates.org:max": true}
		at.Execute(nil, "")
	}
	cd := &CallDescriptor{
		Category:     "call",
		Tenant:       "cgrates.org",
		Subject:      "max",
		Account:      "max",
		Destination:  "0723123113",
		TimeStart:    time.Date(2015, 3, 23, 19, 0, 0, 0, time.UTC),
		TimeEnd:      time.Date(2015, 3, 23, 19, 30, 0, 0, time.UTC),
		MaxCostSoFar: 0,
	}
	cc, err := cd.MaxDebit(nil)
	expected := 10.0
	if cc.Cost != expected || err != nil {
		t.Errorf("Expected %v was %v", expected, cc.Cost)
	}
}

func TestGetCostWithMaxCostFree(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP10_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"cgrates.org:max": true}
		at.Execute(nil, "")
	}
	cd := &CallDescriptor{
		Category:     "call",
		Tenant:       "cgrates.org",
		Subject:      "max",
		Account:      "max",
		Destination:  "0723123113",
		TimeStart:    time.Date(2015, 3, 23, 19, 0, 0, 0, time.UTC),
		TimeEnd:      time.Date(2015, 3, 23, 19, 30, 0, 0, time.UTC),
		MaxCostSoFar: 0,
	}

	cc, err := cd.GetCost()
	expected := 10.0
	if cc.Cost != expected || err != nil {
		t.Errorf("Expected %v was %v", expected, cc.Cost)
	}
}

func TestMaxSessionTimeWithAccountShared(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP_SHARED0_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"vdf:empty0": true}
		at.Execute(nil, "")
	}
	ap, _ = dm.GetActionPlan("TOPUP_SHARED10_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"vdf:empty10": true}
		at.Execute(nil, "")
	}

	cd0 := &CallDescriptor{
		TimeStart:   time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC),
		TimeEnd:     time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC),
		Category:    "0",
		Tenant:      "vdf",
		Subject:     "rif",
		Account:     "empty0",
		Destination: "0723",
	}

	cd1 := &CallDescriptor{
		TimeStart:   time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC),
		TimeEnd:     time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC),
		Category:    "0",
		Tenant:      "vdf",
		Subject:     "rif",
		Account:     "empty10",
		Destination: "0723",
	}

	result0, err := cd0.GetMaxSessionDuration(nil)
	result1, err := cd1.GetMaxSessionDuration(nil)
	if result0 != result1/2 || err != nil {
		t.Errorf("Expected %v was %v, %v", result1/2, result0, err)
	}
}

func TestMaxDebitWithAccountShared(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP_SHARED0_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"vdf:empty0": true}
		at.Execute(nil, "")
	}
	ap, _ = dm.GetActionPlan("TOPUP_SHARED10_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"vdf:empty10": true}
		at.Execute(nil, "")
	}

	cd := &CallDescriptor{
		TimeStart:   time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC),
		TimeEnd:     time.Date(2013, 10, 21, 18, 34, 5, 0, time.UTC),
		Category:    "0",
		Tenant:      "vdf",
		Subject:     "minu",
		Account:     "empty0",
		Destination: "0723",
	}

	cc, err := cd.MaxDebit(nil)
	if err != nil || cc.Cost != 2.5 {
		t.Errorf("Wrong callcost in shared debit: %+v, %v", cc, err)
	}
	acc, _ := cd.getAccount()
	balanceMap := acc.BalanceMap[utils.MetaMonetary]
	if len(balanceMap) != 1 || balanceMap[0].GetValue() != 0 {
		t.Errorf("Wrong shared balance debited: %+v", balanceMap[0])
	}
	other, err := dm.GetAccount("vdf:empty10")
	if err != nil || other.BalanceMap[utils.MetaMonetary][0].GetValue() != 7.5 {
		t.Errorf("Error debiting shared balance: %+v", other.BalanceMap[utils.MetaMonetary][0])
	}
	cd.account.Disabled = true
	if _, err := cd.getAccount(); err == nil || err != utils.ErrAccountDisabled {
		t.Errorf("expected %v,received %v", utils.ErrAccountDisabled, err)
	}
}

func TestMaxSessionTimeWithAccountAccount(t *testing.T) {
	cd := &CallDescriptor{
		TimeStart:   time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC),
		TimeEnd:     time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC),
		Category:    "0",
		Tenant:      "vdf",
		Subject:     "minu_from_tm",
		Account:     "minu",
		Destination: "0723",
	}
	result, err := cd.GetMaxSessionDuration(nil)
	expected := time.Minute
	if result != expected || err != nil {
		t.Errorf("Expected %v was %v", expected, result)
	}
}

func TestMaxSessionTimeNoCredit(t *testing.T) {
	cd := &CallDescriptor{
		TimeStart:   time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC),
		TimeEnd:     time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC),
		Category:    "0",
		Tenant:      "vdf",
		Subject:     "broker",
		Destination: "0723",
		ToR:         utils.MetaVoice,
	}
	result, err := cd.GetMaxSessionDuration(nil)
	if result != time.Minute || err != nil {
		t.Errorf("Expected %v was %v", time.Minute, result)
	}
}

func TestMaxSessionModifiesCallDesc(t *testing.T) {
	t1 := time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC)
	t2 := time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC)
	cd := &CallDescriptor{
		TimeStart:     t1,
		TimeEnd:       t2,
		Category:      "0",
		Tenant:        "vdf",
		Subject:       "minu_from_tm",
		Account:       "minu",
		Destination:   "0723",
		DurationIndex: t2.Sub(t1),
		ToR:           utils.MetaVoice,
	}
	initial := cd.Clone()
	_, err := cd.GetMaxSessionDuration(nil)
	if err != nil {
		t.Error("Got error from max duration: ", err)
	}
	cd.account = nil // it's OK to cache the account
	if !reflect.DeepEqual(cd, initial) {
		t.Errorf("GetMaxSessionDuration is changing the call descriptor %+v != %+v", cd, initial)
	}
}

func TestMaxDebitDurationNoGreatherThanInitialDuration(t *testing.T) {
	cd := &CallDescriptor{
		TimeStart:   time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC),
		TimeEnd:     time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC),
		Category:    "0",
		Tenant:      "vdf",
		Subject:     "minu_from_tm",
		Account:     "minu",
		Destination: "0723",
	}
	initialDuration := cd.TimeEnd.Sub(cd.TimeStart)
	result, err := cd.GetMaxSessionDuration(nil)
	if err != nil {
		t.Error("Got error from max duration: ", err)
	}
	if result > initialDuration {
		t.Error("max session duration greather than initial duration", initialDuration, result)
	}
}

func TestDebitAndMaxDebit(t *testing.T) {
	cd1 := &CallDescriptor{
		TimeStart:   time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC),
		TimeEnd:     time.Date(2013, 10, 21, 18, 34, 10, 0, time.UTC),
		Category:    "0",
		Tenant:      "vdf",
		Subject:     "minu_from_tm",
		Account:     "minu",
		Destination: "0723",
	}
	cd2 := cd1.Clone()
	cc1, err1 := cd1.Debit(nil)
	cc2, err2 := cd2.MaxDebit(nil)
	if err1 != nil || err2 != nil {
		t.Error("Error debiting and/or maxdebiting: ", err1, err2)
	}
	if cc1.Timespans[0].Increments[0].BalanceInfo.Unit.Value != 90*float64(time.Second) ||
		cc2.Timespans[0].Increments[0].BalanceInfo.Unit.Value != 80*float64(time.Second) {
		t.Error("Error setting the Unit.Value: ",
			cc1.Timespans[0].Increments[0].BalanceInfo.Unit.Value,
			cc2.Timespans[0].Increments[0].BalanceInfo.Unit.Value)
	}
	// make Unit.Values have the same value
	cc1.Timespans[0].Increments[0].BalanceInfo.Unit.Value = 0
	cc2.Timespans[0].Increments[0].BalanceInfo.Unit.Value = 0
	cc1.AccountSummary, cc2.AccountSummary = nil, nil // account changes, enforce here emptying the info so reflect can pass
	if !reflect.DeepEqual(cc1.Timespans, cc2.Timespans) {
		t.Log("CC1: ", utils.ToIJSON(cc1))
		t.Log("CC2: ", utils.ToIJSON(cc2))
		t.Error("Debit and MaxDebit differ")
	}
}

func TestMaxSesionTimeEmptyBalance(t *testing.T) {
	cd := &CallDescriptor{
		TimeStart:   time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC),
		TimeEnd:     time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC),
		Category:    "0",
		Tenant:      "vdf",
		Subject:     "minu_from_tm",
		Account:     "luna",
		Destination: "0723",
	}
	acc, _ := dm.GetAccount("vdf:luna")
	allowedTime, err := cd.getMaxSessionDuration(acc, nil)
	if err != nil || allowedTime != 0 {
		t.Error("Error get max session for 0 acount", err)
	}
}

func TestMaxSesionTimeEmptyBalanceAndNoCost(t *testing.T) {
	cd := &CallDescriptor{
		TimeStart:   time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC),
		TimeEnd:     time.Date(2013, 10, 21, 18, 35, 0, 0, time.UTC),
		Category:    "0",
		Tenant:      "vdf",
		Subject:     "one",
		Account:     "luna",
		Destination: "112",
	}
	acc, _ := dm.GetAccount("vdf:luna")
	allowedTime, err := cd.getMaxSessionDuration(acc, nil)
	if err != nil || allowedTime == 0 {
		t.Error("Error get max session for 0 acount", err)
	}
}

func TestMaxSesionTimeLong(t *testing.T) {
	cd := &CallDescriptor{
		TimeStart:   time.Date(2015, 07, 24, 13, 37, 0, 0, time.UTC),
		TimeEnd:     time.Date(2015, 07, 24, 15, 37, 0, 0, time.UTC),
		Category:    "call",
		Tenant:      "cgrates.org",
		Subject:     "money",
		Destination: "0723",
	}
	acc, _ := dm.GetAccount("cgrates.org:money")
	allowedTime, err := cd.getMaxSessionDuration(acc, nil)
	if err != nil || allowedTime != cd.TimeEnd.Sub(cd.TimeStart) {
		t.Error("Error get max session for acount:", allowedTime, err)
	}
}

func TestMaxSesionTimeLongerThanMoney(t *testing.T) {
	cd := &CallDescriptor{
		TimeStart:   time.Date(2015, 07, 24, 13, 37, 0, 0, time.UTC),
		TimeEnd:     time.Date(2015, 07, 24, 16, 37, 0, 0, time.UTC),
		Category:    "call",
		Tenant:      "cgrates.org",
		Subject:     "money",
		Destination: "0723",
	}
	acc, _ := dm.GetAccount("cgrates.org:money")
	allowedTime, err := cd.getMaxSessionDuration(acc, nil)
	expected, err := time.ParseDuration("9999s") // 1 is the connect fee
	if err != nil || allowedTime != expected {
		t.Log(utils.ToIJSON(acc))
		t.Errorf("Expected: %v got %v", expected, allowedTime)
	}
}

func TestDebitFromShareAndNormal(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP_SHARED10_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"vdf:empty10": true}
		at.Execute(nil, "")
	}

	cd := &CallDescriptor{
		TimeStart:   time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC),
		TimeEnd:     time.Date(2013, 10, 21, 18, 34, 5, 0, time.UTC),
		Category:    "0",
		Tenant:      "vdf",
		Subject:     "rif",
		Account:     "empty10",
		Destination: "0723",
	}
	cc, err := cd.MaxDebit(nil)
	acc, _ := cd.getAccount()
	balanceMap := acc.BalanceMap[utils.MetaMonetary]
	if err != nil || cc.Cost != 2.5 {
		t.Errorf("Debit from share and normal error: %+v, %v", cc, err)
	}

	if balanceMap[0].GetValue() != 10 || balanceMap[1].GetValue() != 27.5 {
		t.Errorf("Error debiting from right balance: %v %v", balanceMap[0].GetValue(), balanceMap[1].GetValue())
	}
}

func TestDebitFromEmptyShare(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP_EMPTY_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"vdf:emptyX": true}
		at.Execute(nil, "")
	}

	cd := &CallDescriptor{
		TimeStart:   time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC),
		TimeEnd:     time.Date(2013, 10, 21, 18, 34, 5, 0, time.UTC),
		Category:    "0",
		Tenant:      "vdf",
		Subject:     "rif",
		Account:     "emptyX",
		Destination: "0723",
	}

	cc, err := cd.MaxDebit(nil)
	if err != nil || cc.Cost != 2.5 {
		t.Errorf("Debit from empty share error: %+v, %v", cc, err)
	}
	acc, _ := cd.getAccount()
	balanceMap := acc.BalanceMap[utils.MetaMonetary]
	if len(balanceMap) != 2 || balanceMap[0].GetValue() != 0 || balanceMap[1].GetValue() != -2.5 {
		t.Errorf("Error debiting from empty share: %+v", balanceMap[1].GetValue())
	}
}

func TestDebitNegatve(t *testing.T) {
	ap, _ := dm.GetActionPlan("POST_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"vdf:post": true}
		at.Execute(nil, "")
	}

	cd := &CallDescriptor{
		TimeStart:   time.Date(2013, 10, 21, 18, 34, 0, 0, time.UTC),
		TimeEnd:     time.Date(2013, 10, 21, 18, 34, 5, 0, time.UTC),
		Category:    "0",
		Tenant:      "vdf",
		Subject:     "rif",
		Account:     "post",
		Destination: "0723",
	}
	cc, err := cd.MaxDebit(nil)
	//utils.PrintFull(cc)
	if err != nil || cc.Cost != 2.5 {
		t.Errorf("Debit from empty share error: %+v, %v", cc, err)
	}
	acc, _ := cd.getAccount()
	//utils.PrintFull(acc)
	balanceMap := acc.BalanceMap[utils.MetaMonetary]
	if len(balanceMap) != 1 || balanceMap[0].GetValue() != -2.5 {
		t.Errorf("Error debiting from empty share: %+v", balanceMap[0].GetValue())
	}
	cc, err = cd.MaxDebit(nil)
	acc, _ = cd.getAccount()
	balanceMap = acc.BalanceMap[utils.MetaMonetary]
	//utils.LogFull(balanceMap)
	if err != nil || cc.Cost != 2.5 {
		t.Errorf("Debit from empty share error: %+v, %v", cc, err)
	}
	if len(balanceMap) != 1 || balanceMap[0].GetValue() != -5 {
		t.Errorf("Error debiting from empty share: %+v", balanceMap[0].GetValue())
	}
}

func TestMaxDebitZeroDefinedRate(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP10_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"cgrates.org:12345": true}
		at.Execute(nil, "")
	}
	cd1 := &CallDescriptor{
		Category:      "call",
		Tenant:        "cgrates.org",
		Subject:       "12345",
		Account:       "12345",
		Destination:   "447956",
		TimeStart:     time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC),
		TimeEnd:       time.Date(2014, 3, 4, 6, 1, 0, 0, time.UTC),
		LoopIndex:     0,
		DurationIndex: 0}
	cc, err := cd1.MaxDebit(nil)
	if err != nil {
		t.Error("Error maxdebiting: ", err)
	}
	if cc.GetDuration() != 49*time.Second {
		t.Error("Error obtaining max debit duration: ", cc.GetDuration())
	}

	if cc.Cost != 0.91 {
		t.Error("Error in max debit cost: ", cc.Cost)
	}
}

func TestMaxDebitForceDuration(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP10_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"cgrates.org:12345": true}
		at.Execute(nil, "")
	}
	cd1 := &CallDescriptor{
		Category:      "call",
		Tenant:        "cgrates.org",
		Subject:       "12345",
		Account:       "12345",
		Destination:   "447956",
		TimeStart:     time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC),
		TimeEnd:       time.Date(2014, 3, 4, 6, 1, 40, 0, time.UTC),
		LoopIndex:     0,
		DurationIndex: 0,
		ForceDuration: true,
	}
	_, err := cd1.MaxDebit(nil)
	if err != utils.ErrInsufficientCredit {
		t.Fatal("Error forcing duration: ", err)
	}
}

func TestMaxDebitZeroDefinedRateOnlyMinutes(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP10_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"cgrates.org:12345": true}
		at.Execute(nil, "")
	}
	cd1 := &CallDescriptor{
		Category:      "call",
		Tenant:        "cgrates.org",
		Subject:       "12345",
		Account:       "12345",
		Destination:   "447956",
		TimeStart:     time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC),
		TimeEnd:       time.Date(2014, 3, 4, 6, 0, 40, 0, time.UTC),
		LoopIndex:     0,
		DurationIndex: 0}
	cc, err := cd1.MaxDebit(nil)
	if err != nil {
		t.Fatal("Error maxdebiting: ", err)
	}
	if cc.GetDuration() != 40*time.Second {
		t.Error("Error obtaining max debit duration: ", cc.GetDuration())
	}
	if cc.Cost != 0.01 {
		t.Error("Error in max debit cost: ", cc.Cost)
	}
}

func TestMaxDebitConsumesMinutes(t *testing.T) {
	ap, _ := dm.GetActionPlan("TOPUP10_AT", true, true, utils.NonTransactional)
	for _, at := range ap.ActionTimings {
		at.accountIDs = utils.StringMap{"cgrates.org:12345": true}
		at.Execute(nil, "")
	}
	cd1 := &CallDescriptor{
		Category:      "call",
		Tenant:        "cgrates.org",
		Subject:       "12345",
		Account:       "12345",
		Destination:   "447956",
		TimeStart:     time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC),
		TimeEnd:       time.Date(2014, 3, 4, 6, 0, 5, 0, time.UTC),
		LoopIndex:     0,
		DurationIndex: 0}
	cd1.MaxDebit(nil)
	if cd1.account.BalanceMap[utils.MetaVoice][0].GetValue() != 20*float64(time.Second) {
		t.Error("Error using minutes: ",
			cd1.account.BalanceMap[utils.MetaVoice][0].GetValue())
	}
}

func TestCDGetCostANY(t *testing.T) {
	cd1 := &CallDescriptor{
		Category:    "data",
		Tenant:      "cgrates.org",
		Subject:     "rif",
		Destination: utils.MetaAny,
		TimeStart:   time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC),
		TimeEnd:     time.Date(2014, 3, 4, 6, 0, 1, 0, time.UTC),
		ToR:         utils.MetaData,
	}
	cc, err := cd1.GetCost()
	if err != nil || cc.Cost != 60 {
		t.Errorf("Error getting *any dest: %+v %v", cc, err)
	}
}

func TestCDSplitInDataSlots(t *testing.T) {
	cd := &CallDescriptor{
		Category:      "data",
		Tenant:        "cgrates.org",
		Subject:       "rif",
		Destination:   utils.MetaAny,
		TimeStart:     time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC),
		TimeEnd:       time.Date(2014, 3, 4, 6, 1, 5, 0, time.UTC),
		ToR:           utils.MetaData,
		DurationIndex: 65 * time.Second,
	}
	cd.LoadRatingPlans()
	timespans := cd.splitInTimeSpans()
	if len(timespans) != 2 {
		t.Log(cd.RatingInfos[0])
		t.Error("Wrong number of timespans: ", len(timespans))
	}
}

func TestCDDataGetCost(t *testing.T) {
	cd := &CallDescriptor{
		Category:    "data",
		Tenant:      "cgrates.org",
		Subject:     "rif",
		Destination: utils.MetaAny,
		TimeStart:   time.Date(2014, 3, 4, 6, 0, 0, 0, time.UTC),
		TimeEnd:     time.Date(2014, 3, 4, 6, 1, 5, 0, time.UTC),
		ToR:         utils.MetaData,
	}
	cc, err := cd.GetCost()
	if err != nil || cc.Cost != 65 {
		t.Errorf("Error getting *any dest: %+v %v", cc, err)
	}
}

func TestCDRefundIncrements(t *testing.T) {
	ub := &Account{
		ID: "test:ref",
		BalanceMap: map[string]Balances{
			utils.MetaMonetary: {
				&Balance{Uuid: "moneya", Value: 100},
			},
			utils.MetaVoice: {
				&Balance{Uuid: "minutea",
					Value:          10 * float64(time.Second),
					Weight:         20,
					DestinationIDs: utils.StringMap{"NAT": true}},
				&Balance{Uuid: "minuteb",
					Value:          10 * float64(time.Second),
					DestinationIDs: utils.StringMap{"RET": true}},
			},
		},
	}
	dm.SetAccount(ub)
	increments := Increments{
		&Increment{
			Cost: 2,
			BalanceInfo: &DebitInfo{
				Monetary: &MonetaryInfo{
					UUID: "moneya",
				},
				AccountID: ub.ID,
			},
		},
		&Increment{
			Cost:     2,
			Duration: 3 * time.Second,
			BalanceInfo: &DebitInfo{
				Unit: &UnitInfo{
					UUID:   "minutea",
					Factor: 1,
				},
				Monetary: &MonetaryInfo{
					UUID: "moneya",
				},
				AccountID: ub.ID,
			},
		},
		&Increment{
			Duration: 4 * time.Second,
			BalanceInfo: &DebitInfo{
				Unit: &UnitInfo{
					UUID:   "minuteb",
					Factor: 1,
				},
				AccountID: ub.ID,
			},
		},
	}
	cd := &CallDescriptor{ToR: utils.MetaVoice, Increments: increments}
	cd.RefundIncrements(nil)
	ub, _ = dm.GetAccount(ub.ID)
	if ub.BalanceMap[utils.MetaMonetary][0].GetValue() != 104 ||
		ub.BalanceMap[utils.MetaVoice][0].GetValue() != 13*float64(time.Second) ||
		ub.BalanceMap[utils.MetaVoice][1].GetValue() != 14*float64(time.Second) {
		t.Error("Error refunding money: ", utils.ToIJSON(ub.BalanceMap))
	}
}

func TestCDRefundIncrementsZeroValue(t *testing.T) {
	ub := &Account{
		ID: "test:ref",
		BalanceMap: map[string]Balances{
			utils.MetaMonetary: {
				&Balance{Uuid: "moneya", Value: 100},
			},
			utils.MetaVoice: {
				&Balance{Uuid: "minutea", Value: 10, Weight: 20, DestinationIDs: utils.StringMap{"NAT": true}},
				&Balance{Uuid: "minuteb", Value: 10, DestinationIDs: utils.StringMap{"RET": true}},
			},
		},
	}
	dm.SetAccount(ub)
	increments := Increments{
		&Increment{Cost: 0, BalanceInfo: &DebitInfo{AccountID: ub.ID}},
		&Increment{Cost: 0, Duration: 3 * time.Second, BalanceInfo: &DebitInfo{AccountID: ub.ID}},
		&Increment{Cost: 0, Duration: 4 * time.Second, BalanceInfo: &DebitInfo{AccountID: ub.ID}},
	}
	cd := &CallDescriptor{ToR: utils.MetaVoice, Increments: increments}
	cd.RefundIncrements(nil)
	ub, _ = dm.GetAccount(ub.ID)
	if ub.BalanceMap[utils.MetaMonetary][0].GetValue() != 100 ||
		ub.BalanceMap[utils.MetaVoice][0].GetValue() != 10 ||
		ub.BalanceMap[utils.MetaVoice][1].GetValue() != 10 {
		t.Error("Error refunding money: ", utils.ToIJSON(ub.BalanceMap))
	}
}

func TestCDDebitBalanceSubjectWithFallback(t *testing.T) {
	acnt := &Account{
		ID: "TCDDBSWF:account1",
		BalanceMap: map[string]Balances{
			utils.MetaVoice: {
				&Balance{ID: "voice1", Value: 60 * float64(time.Second),
					RatingSubject: "SubjTCDDBSWF"},
			}},
	}
	dm.SetAccount(acnt)
	dst := &Destination{Id: "DST_TCDDBSWF", Prefixes: []string{"1716"}}
	dm.SetDestination(dst, utils.NonTransactional)
	dm.SetReverseDestination(dst.Id, dst.Prefixes, utils.NonTransactional)
	rpSubj := &RatingPlan{
		Id: "RP_TCDDBSWF",
		Timings: map[string]*RITiming{
			"30eab300": {
				Years:     utils.Years{},
				Months:    utils.Months{},
				MonthDays: utils.MonthDays{},
				WeekDays:  utils.WeekDays{},
				StartTime: "00:00:00",
			},
		},
		Ratings: map[string]*RIRate{
			"b457f86d": {

				Rates: []*RGRate{
					{
						GroupIntervalStart: 0,
						Value:              0,
						RateIncrement:      60 * time.Second,
						RateUnit:           60 * time.Second,
					},
				},
				RoundingMethod:   utils.MetaRoundingMiddle,
				RoundingDecimals: 4,
			},
		},
		DestinationRates: map[string]RPRateList{
			dst.Id: []*RPRate{
				{
					Timing: "30eab300",
					Rating: "b457f86d",
					Weight: 10,
				},
			},
		},
	}
	rpDflt := &RatingPlan{
		Id: "RP_DFLT",
		Timings: map[string]*RITiming{
			"30eab301": {
				Years:     utils.Years{},
				Months:    utils.Months{},
				MonthDays: utils.MonthDays{},
				WeekDays:  utils.WeekDays{},
				StartTime: "00:00:00",
			},
		},
		Ratings: map[string]*RIRate{
			"b457f861": {
				Rates: []*RGRate{
					{
						GroupIntervalStart: 0,
						Value:              0.01,
						RateIncrement:      time.Second,
						RateUnit:           time.Second,
					},
				},
				RoundingMethod:   utils.MetaRoundingMiddle,
				RoundingDecimals: 4,
			},
		},
		DestinationRates: map[string]RPRateList{
			dst.Id: []*RPRate{
				{
					Timing: "30eab301",
					Rating: "b457f861",
					Weight: 10,
				},
			},
		},
	}
	for _, rpl := range []*RatingPlan{rpSubj, rpDflt} {
		dm.SetRatingPlan(rpl)
	}
	rpfTCDDBSWF := &RatingProfile{Id: "*out:TCDDBSWF:call:SubjTCDDBSWF",
		RatingPlanActivations: RatingPlanActivations{&RatingPlanActivation{
			ActivationTime: time.Date(2015, 01, 01, 8, 0, 0, 0, time.UTC),
			RatingPlanId:   rpSubj.Id,
		}},
	}
	rpfAny := &RatingProfile{Id: "*out:TCDDBSWF:call:*any",
		RatingPlanActivations: RatingPlanActivations{&RatingPlanActivation{
			ActivationTime: time.Date(2015, 01, 01, 8, 0, 0, 0, time.UTC),
			RatingPlanId:   rpDflt.Id,
		}},
	}
	for _, rpf := range []*RatingProfile{rpfTCDDBSWF, rpfAny} {
		dm.SetRatingProfile(rpf)
	}
	cd1 := &CallDescriptor{ // test the cost for subject within balance setup
		Category:    "call",
		Tenant:      "TCDDBSWF",
		Subject:     "SubjTCDDBSWF",
		Destination: "1716",
		TimeStart:   time.Date(2015, 01, 01, 9, 0, 0, 0, time.UTC),
		TimeEnd:     time.Date(2015, 01, 01, 9, 2, 0, 0, time.UTC),
		ToR:         utils.MetaVoice,
	}
	if cc, err := cd1.GetCost(); err != nil || cc.Cost != 0 {
		t.Errorf("Error getting *any dest: %+v %v", cc, err)
	}
	cd2 := &CallDescriptor{ // equivalent of the extra charge out of *monetary balance when *voice is finished
		Category:    "call",
		Tenant:      "TCDDBSWF",
		Subject:     "dan",
		Destination: "1716",
		TimeStart:   time.Date(2015, 01, 01, 9, 1, 0, 0, time.UTC),
		TimeEnd:     time.Date(2015, 01, 01, 9, 2, 0, 0, time.UTC),
		ToR:         utils.MetaVoice,
	}
	if cc, err := cd2.GetCost(); err != nil || cc.Cost != 0.6 {
		t.Errorf("Error getting *any dest: %+v %v", cc, err)
	}
	cd := &CallDescriptor{ // test the cost
		Category:    "call",
		Tenant:      "TCDDBSWF",
		Account:     "account1",
		Subject:     "account1",
		Destination: "1716",
		TimeStart:   time.Date(2015, 01, 01, 9, 0, 0, 0, time.UTC),
		TimeEnd:     time.Date(2015, 01, 01, 9, 2, 0, 0, time.UTC),
		ToR:         utils.MetaVoice,
	}
	if cc, err := cd.Debit(nil); err != nil {
		t.Error(err)
	} else if cc.Cost != 0.6 {
		t.Errorf("CallCost: %v", cc)
	}
	if resAcnt, err := dm.GetAccount(acnt.ID); err != nil {
		t.Error(err)
	} else if resAcnt.BalanceMap[utils.MetaVoice][0].ID != "voice1" ||
		resAcnt.BalanceMap[utils.MetaVoice][0].Value != 0 {
		t.Errorf("Account: %v", resAcnt)
	} else if len(resAcnt.BalanceMap[utils.MetaMonetary]) == 0 ||
		resAcnt.BalanceMap[utils.MetaMonetary][0].ID != utils.MetaDefault ||
		resAcnt.BalanceMap[utils.MetaMonetary][0].Value != -0.600013 { // rounding issue
		t.Errorf("Account: %s", utils.ToIJSON(resAcnt))
	}
}
func TestCallDescriptorUpdateFromCGREvent(t *testing.T) {
	cgrEv := &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "Generated",
		Event: map[string]any{
			"Account":     "acc1",
			"AnswerTime":  time.Date(2015, 3, 23, 6, 0, 0, 0, time.UTC),
			"Category":    "call",
			"Destination": "0723123113",
			"Subject":     "acc1",
			"Tenant":      "cgrates.org",
			"ToR":         "",
			"Usage":       30 * time.Minute,
			"Extra":       "Value",
		},
	}
	cd := &CallDescriptor{
		Category:     "call",
		Tenant:       "cgrates.org",
		Subject:      "max",
		Account:      "max",
		Destination:  "0723123113",
		TimeStart:    time.Date(2015, 3, 23, 6, 0, 0, 0, time.UTC),
		TimeEnd:      time.Date(2015, 3, 23, 6, 30, 0, 0, time.UTC),
		MaxCostSoFar: 0,
		ExtraFields: map[string]string{
			"Extra": "Value",
		},
	}
	cdExpected := &CallDescriptor{
		Category:     "call",
		Tenant:       "cgrates.org",
		Subject:      "acc1",
		Account:      "acc1",
		Destination:  "0723123113",
		TimeStart:    time.Date(2015, 3, 23, 6, 0, 0, 0, time.UTC),
		TimeEnd:      time.Date(2015, 3, 23, 6, 30, 0, 0, time.UTC),
		MaxCostSoFar: 0,
		ExtraFields: map[string]string{
			"Extra": "Value",
		},
	}
	if err := cd.UpdateFromCGREvent(cgrEv, []string{utils.Usage, utils.AnswerTime, utils.Destination, utils.Category, utils.ToR, utils.Tenant, utils.AccountField, utils.Subject}); err != nil {
		t.Error(err)
	} else {
		if !reflect.DeepEqual(cd, cdExpected) {
			t.Errorf("Expecting: %+v, received: %+v", cdExpected, cd)
		}
	}
	cgrEv = &utils.CGREvent{
		Tenant: "cgrates.org",
		ID:     "Generated",
		Event: map[string]any{
			"Account":     "acc1",
			"AnswerTime":  time.Date(2015, 3, 23, 6, 0, 0, 0, time.UTC),
			"Category":    "call",
			"Destination": "0723123113",
			"Subject":     "acc1",
			"Tenant":      "cgrates.org",
			"ToR":         "",
			"Usage":       40 * time.Minute,
		},
	}
	if err := cd.UpdateFromCGREvent(cgrEv, []string{utils.AccountField, utils.Subject}); err != nil {
		t.Error(err)
	} else {
		if !reflect.DeepEqual(cd, cdExpected) {
			t.Errorf("Expecting: %+v, received: %+v", cdExpected, cd)
		}
	}
	cgrEv = &utils.CGREvent{}
	if err = cd.UpdateFromCGREvent(cgrEv, []string{utils.Usage}); err == nil {
		t.Error(err)
	} else if err = cd.UpdateFromCGREvent(cgrEv, []string{utils.AnswerTime}); err == nil {
		t.Error(err)
	} else if err = cd.UpdateFromCGREvent(cgrEv, []string{utils.Destination}); err == nil {
		t.Error(err)
	} else if err = cd.UpdateFromCGREvent(cgrEv, []string{utils.Category}); err == nil {
		t.Error(err)
	} else if err = cd.UpdateFromCGREvent(cgrEv, []string{utils.ToR}); err == nil {
		t.Error(err)
	} else if err = cd.UpdateFromCGREvent(cgrEv, []string{utils.Tenant}); err == nil {
		t.Error(err)
	} else if err = cd.UpdateFromCGREvent(cgrEv, []string{utils.AccountField}); err == nil {
		t.Error(err)
	} else if err = cd.UpdateFromCGREvent(cgrEv, []string{utils.Subject}); err == nil {
		t.Error(err)
	} else if err = cd.UpdateFromCGREvent(cgrEv, []string{"Extra"}); err == nil {
		t.Error(err)
	}
	cgrEv = &utils.CGREvent{
		Event: map[string]any{
			"Extra": "Value",
		},
	}
	cd = &CallDescriptor{
		ExtraFields: map[string]string{
			"Extra": "Value",
		},
	}
	cdExpected = &CallDescriptor{
		ExtraFields: map[string]string{
			"Extra": "Value",
		},
	}
	if err = cd.UpdateFromCGREvent(cgrEv, []string{"Extra"}); err != nil {
		t.Error(err)
	} else if !reflect.DeepEqual(cd, cdExpected) {
		t.Errorf("expected %v ,received %v", utils.ToJSON(cd), utils.ToJSON(cdExpected))
	}
}

func TestCallDescriptorAsCGREvent(t *testing.T) {
	cd := &CallDescriptor{
		Category:     "call",
		Tenant:       "cgrates.org",
		Subject:      "max",
		Account:      "max",
		Destination:  "0723123113",
		TimeStart:    time.Date(2015, 3, 23, 6, 0, 0, 0, time.UTC),
		TimeEnd:      time.Date(2015, 3, 23, 6, 30, 0, 0, time.UTC),
		MaxCostSoFar: 0,
	}
	eCGREvent := &utils.CGREvent{Tenant: "cgrates.org",
		ID: "Generated",
		Event: map[string]any{
			"Account":     "max",
			"AnswerTime":  time.Date(2015, 3, 23, 6, 0, 0, 0, time.UTC),
			"Category":    "call",
			"Destination": "0723123113",
			"Subject":     "max",
			"Tenant":      "cgrates.org",
			"ToR":         "",
			"Usage":       30 * time.Minute,
		},
	}
	cgrEvent := cd.AsCGREvent(nil)
	if !reflect.DeepEqual(eCGREvent.Tenant, cgrEvent.Tenant) {
		t.Errorf("Expecting: %+v, received: %+v", eCGREvent.Tenant, cgrEvent.Tenant)
	}
	for fldName, fldVal := range eCGREvent.Event {
		if _, has := cgrEvent.Event[fldName]; !has {
			t.Errorf("Expecting: %+v, received: %+v", fldName, nil)
		} else if fldVal != cgrEvent.Event[fldName] {
			t.Errorf("Expecting: %s:%+v, received: %s:%+v", fldName, eCGREvent.Event[fldName], fldName, cgrEvent.Event[fldName])
		}
	}
}

func TestCalldescRefundIncrementsNoBalanceInfo(t *testing.T) {
	cd := &CallDescriptor{
		Category:    "call",
		Tenant:      "cgrates.org",
		Subject:     "1001",
		Account:     "1001",
		Destination: "1002",
		Increments: Increments{
			{
				Duration: time.Second,
				Cost:     5,
			},
		},
	}

	rcv, err := cd.RefundIncrements(nil)

	if err != nil {
		t.Fatalf("\nexpected: <%+v>, \nreceived: <%+v>", nil, err)
	}

	if rcv != nil {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", nil, rcv)
	}
}

func TestCalldescAccountSummaryNilAcc(t *testing.T) {
	cd := &CallDescriptor{
		Category:    "call",
		Subject:     "1001",
		Tenant:      "cgrates.org",
		Destination: "1002",
	}
	initialAcc := &AccountSummary{
		Tenant: "cgrates.org",
		ID:     "testID",
	}

	rcv := cd.AccountSummary(initialAcc)

	if rcv != nil {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", nil, rcv)
	}
}

func TestCalldescFieldAsInterfaceNilFieldPath(t *testing.T) {
	cd := &CallDescriptor{
		Category:    "call",
		Subject:     "1001",
		Tenant:      "cgrates.org",
		Destination: "1002",
	}
	fldPath := []string{}

	experr := utils.ErrNotFound
	rcv, err := cd.FieldAsInterface(fldPath)

	if err == nil || err != experr {
		t.Fatalf("\nexpected: <%+v>, \nreceived: <%+v>", experr, err)
	}

	if rcv != nil {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", nil, rcv)
	}
}

func TestCalldescFieldAsStringNilFieldPath(t *testing.T) {
	cd := &CallDescriptor{
		Category:    "call",
		Subject:     "1001",
		Tenant:      "cgrates.org",
		Destination: "1002",
	}
	fldPath := []string{}

	experr := utils.ErrNotFound
	exp := utils.EmptyString
	rcv, err := cd.FieldAsString(fldPath)

	if err == nil || err != experr {
		t.Fatalf("\nexpected: <%+v>, \nreceived: <%+v>", experr, err)
	}

	if rcv != exp {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", exp, rcv)
	}
}

func TestCalldescSetRoundingDecimals(t *testing.T) {
	exp := 6
	SetRoundingDecimals(6)

	if globalRoundingDecimals != exp {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", exp, globalRoundingDecimals)
	}
}

func TestCalldescSetRpSubjectPrefixMatching(t *testing.T) {
	SetRpSubjectPrefixMatching(false)

	if rpSubjectPrefixMatching != false {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>",
			false, rpSubjectPrefixMatching)
	}
}

func TestCalldescNewCallDescriptorFromCGREventNoAccount(t *testing.T) {
	cgrEv := &utils.CGREvent{
		Event: map[string]any{
			"testKey": 5,
		},
	}
	timezone := ""

	experr := utils.ErrNotFound
	exp := &CallDescriptor{}
	rcv, err := NewCallDescriptorFromCGREvent(cgrEv, timezone)

	if err == nil || err != experr {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", experr, err)
	}

	if !reflect.DeepEqual(rcv, exp) {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", exp, rcv)
	}
}

func TestCalldescNewCallDescriptorFromCGREventNoDestination(t *testing.T) {
	cgrEv := &utils.CGREvent{
		Event: map[string]any{
			utils.Category:     "catField",
			utils.AccountField: "accField",
		},
	}
	timezone := ""

	experr := utils.ErrNotFound
	var exp *CallDescriptor
	rcv, err := NewCallDescriptorFromCGREvent(cgrEv, timezone)

	if err == nil || err != experr {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", experr, err)
	}

	if !reflect.DeepEqual(rcv, exp) {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", exp, rcv)
	}
}

func TestCalldescNewCallDescriptorFromCGREventNoTimeStart(t *testing.T) {
	cgrEv := &utils.CGREvent{
		Event: map[string]any{
			utils.Category:     "catField",
			utils.AccountField: "accField",
			utils.Destination:  "destField",
		},
	}
	timezone := ""

	experr := utils.ErrNotFound
	var exp *CallDescriptor
	rcv, err := NewCallDescriptorFromCGREvent(cgrEv, timezone)

	if err == nil || err != experr {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", experr, err)
	}

	if !reflect.DeepEqual(rcv, exp) {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", exp, rcv)
	}
}

func TestCalldescNewCallDescriptorFromCGREventInvalidAnswerTime(t *testing.T) {
	cgrEv := &utils.CGREvent{
		Event: map[string]any{
			utils.Category:     "catField",
			utils.AccountField: "accField",
			utils.Destination:  "destField",
			utils.SetupTime:    time.Date(2021, 1, 1, 23, 59, 59, 0, time.UTC),
			utils.AnswerTime:   5,
		},
	}
	timezone := "UTC"

	experr := "cannot convert field: 5 to time.Time"
	var exp *CallDescriptor
	rcv, err := NewCallDescriptorFromCGREvent(cgrEv, timezone)

	if err == nil || err.Error() != experr {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", experr, err)
	}

	if !reflect.DeepEqual(rcv, exp) {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", exp, rcv)
	}
}

func TestCalldescNewCallDescriptorFromCGREventNoUsage(t *testing.T) {
	cgrEv := &utils.CGREvent{
		Event: map[string]any{
			utils.Category:     "catField",
			utils.AccountField: "accField",
			utils.Destination:  "destField",
			utils.SetupTime:    time.Date(2021, 1, 1, 23, 59, 59, 0, time.UTC),
			utils.AnswerTime:   time.Date(2021, 1, 5, 23, 59, 59, 0, time.UTC),
		},
	}
	timezone := "UTC"

	experr := utils.ErrNotFound
	var exp *CallDescriptor
	rcv, err := NewCallDescriptorFromCGREvent(cgrEv, timezone)

	if err == nil || err != experr {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", experr, err)
	}

	if !reflect.DeepEqual(rcv, exp) {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", exp, rcv)
	}
}

func TestCalldescNewCallDescriptorFromCGREvent(t *testing.T) {
	cgrEv := &utils.CGREvent{
		Event: map[string]any{
			utils.Category:     "catField",
			utils.AccountField: "accField",
			utils.Destination:  "destField",
			utils.SetupTime:    time.Date(2021, 1, 1, 23, 59, 59, 0, time.UTC),
			utils.AnswerTime:   time.Date(2021, 1, 5, 23, 59, 59, 0, time.UTC),
			utils.Usage:        100,
			utils.ToR:          utils.MetaVoice,
		},
		Tenant: "cgrates.org",
	}
	timezone := "UTC"

	exp := &CallDescriptor{
		Category:    "catField",
		Subject:     "accField",
		Account:     "accField",
		Destination: "destField",
		TimeStart:   time.Date(2021, 1, 5, 23, 59, 59, 0, time.UTC),
		TimeEnd:     time.Date(2021, 1, 5, 23, 59, 59, 100, time.UTC),
		ToR:         utils.MetaVoice,
		Tenant:      "cgrates.org",
	}
	rcv, err := NewCallDescriptorFromCGREvent(cgrEv, timezone)

	if err != nil {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", nil, err)
	}

	if !reflect.DeepEqual(rcv, exp) {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", exp, rcv)
	}

}

func TestCalldescAsCGREvent(t *testing.T) {
	cd := &CallDescriptor{
		Category:    "catField",
		Subject:     "accField",
		Account:     "accField",
		Destination: "destField",
		TimeStart:   time.Date(2021, 1, 5, 23, 59, 59, 0, time.UTC),
		TimeEnd:     time.Date(2021, 1, 5, 23, 59, 59, 100, time.UTC),
		ToR:         utils.MetaVoice,
		Tenant:      "cgrates.org",
		ExtraFields: map[string]string{
			"eventKey1": "eventValue1",
			"eventKey2": "eventValue2",
			"eventKey3": "eventValue3",
		},
	}
	opts := make(map[string]any)

	exp := &utils.CGREvent{
		Event: map[string]any{
			utils.Category:     "catField",
			utils.Subject:      "accField",
			utils.AccountField: "accField",
			utils.Destination:  "destField",
			utils.AnswerTime:   time.Date(2021, 1, 5, 23, 59, 59, 0, time.UTC),
			utils.Usage:        100 * time.Nanosecond,
			utils.ToR:          utils.MetaVoice,
			utils.Tenant:       "cgrates.org",
			"eventKey1":        "eventValue1",
			"eventKey2":        "eventValue2",
			"eventKey3":        "eventValue3",
		},
		APIOpts: opts,
		Tenant:  "cgrates.org",
	}
	rcv := cd.AsCGREvent(opts)
	exp.ID = rcv.ID

	if !reflect.DeepEqual(rcv, exp) {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", exp, rcv)
	}
}

func TestCalldescAddRatingInfo(t *testing.T) {
	cd := &CallDescriptor{
		Category:    "call",
		Tenant:      "cgrates.org",
		Subject:     "1001",
		Account:     "1001",
		Destination: "1002",
	}
	ris := []*RatingInfo{
		{
			MatchedSubject: "1001",
			RatingPlanId:   "RP_1001",
			MatchedPrefix:  "1001",
			MatchedDestId:  "1002",
			ActivationTime: time.Date(2021, 1, 5, 23, 59, 59, 0, time.UTC),
			RateIntervals: RateIntervalList{
				{
					Rating: &RIRate{
						ConnectFee:       0.4,
						tag:              "tag",
						RoundingMethod:   "*up",
						RoundingDecimals: 4,
						MaxCost:          100,
						MaxCostStrategy:  "*disconnect",
						Rates: RateGroups{
							{
								Value:         10,
								RateIncrement: 60,
								RateUnit:      60,
							},
						},
					},
				},
			},
			FallbackKeys: []string{"key1", "key2"},
		},
		{
			MatchedSubject: "1002",
			RatingPlanId:   "RP_1002",
			MatchedPrefix:  "1002",
			MatchedDestId:  "1003",
			ActivationTime: time.Date(2021, 1, 5, 23, 59, 59, 0, time.UTC),
			RateIntervals: RateIntervalList{
				{
					Rating: &RIRate{
						ConnectFee:       0.3,
						tag:              "tag",
						RoundingMethod:   "*up",
						RoundingDecimals: 7,
						MaxCost:          150,
						MaxCostStrategy:  "*disconnect",
						Rates: RateGroups{
							{
								Value:         10,
								RateIncrement: 1,
								RateUnit:      60,
							},
						},
					},
				},
			},
			FallbackKeys: []string{"key3"},
		},
	}

	exp := &CallDescriptor{
		Category:    "call",
		Tenant:      "cgrates.org",
		Subject:     "1001",
		Account:     "1001",
		Destination: "1002",
		RatingInfos: RatingInfos{
			{
				MatchedSubject: "1001",
				RatingPlanId:   "RP_1001",
				MatchedPrefix:  "1001",
				MatchedDestId:  "1002",
				ActivationTime: time.Date(2021, 1, 5, 23, 59, 59, 0, time.UTC),
				RateIntervals: RateIntervalList{
					{
						Rating: &RIRate{
							ConnectFee:       0.4,
							tag:              "tag",
							RoundingMethod:   "*up",
							RoundingDecimals: 4,
							MaxCost:          100,
							MaxCostStrategy:  "*disconnect",
							Rates: RateGroups{
								{
									Value:         10,
									RateIncrement: 60,
									RateUnit:      60,
								},
							},
						},
					},
				},
				FallbackKeys: []string{"key1", "key2"},
			},
			{
				MatchedSubject: "1002",
				RatingPlanId:   "RP_1002",
				MatchedPrefix:  "1002",
				MatchedDestId:  "1003",
				ActivationTime: time.Date(2021, 1, 5, 23, 59, 59, 0, time.UTC),
				RateIntervals: RateIntervalList{
					{
						Rating: &RIRate{
							ConnectFee:       0.3,
							tag:              "tag",
							RoundingMethod:   "*up",
							RoundingDecimals: 7,
							MaxCost:          150,
							MaxCostStrategy:  "*disconnect",
							Rates: RateGroups{
								{
									Value:         10,
									RateIncrement: 1,
									RateUnit:      60,
								},
							},
						},
					},
				},
				FallbackKeys: []string{"key3"},
			},
		},
	}
	cd.AddRatingInfo(ris[0], ris[1])

	if !reflect.DeepEqual(cd, exp) {
		t.Errorf("\nexpected: <%+v>, \nreceived: <%+v>", exp, cd)
	}

}

/*************** BENCHMARKS ********************/
func BenchmarkStorageGetting(b *testing.B) {
	b.StopTimer()
	t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC)
	t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "vdf",
		Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2}
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		dm.GetRatingProfile(cd.GetKey(cd.Subject), false, utils.NonTransactional)
	}
}

func BenchmarkStorageRestoring(b *testing.B) {
	b.StopTimer()
	t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC)
	t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "vdf", Subject: "rif",
		Destination: "0256", TimeStart: t1, TimeEnd: t2}
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		cd.LoadRatingPlans()
	}
}

func BenchmarkStorageGetCost(b *testing.B) {
	b.StopTimer()
	t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC)
	t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "vdf", Subject: "rif",
		Destination: "0256", TimeStart: t1, TimeEnd: t2}
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		cd.GetCost()
	}
}

func BenchmarkSplitting(b *testing.B) {
	b.StopTimer()
	t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC)
	t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC)
	cd := &CallDescriptor{Category: "0", Tenant: "vdf", Subject: "rif",
		Destination: "0256", TimeStart: t1, TimeEnd: t2}
	cd.LoadRatingPlans()
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		cd.splitInTimeSpans()
	}
}

func BenchmarkStorageSingleGetSessionTime(b *testing.B) {
	b.StopTimer()
	cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", Destination: "0723"}
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		cd.GetMaxSessionDuration(nil)
	}
}

func BenchmarkStorageMultipleGetSessionTime(b *testing.B) {
	b.StopTimer()
	cd := &CallDescriptor{Category: "0", Tenant: "vdf",
		Subject: "minutosu", Destination: "0723"}
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		cd.GetMaxSessionDuration(nil)
	}
}

func TestCDRefundIncrementspanic(t *testing.T) {
	ub := &Account{
		ID: "test:refpanic",
		BalanceMap: map[string]Balances{
			utils.MetaMonetary: {
				&Balance{Uuid: "moneya", Value: 100},
			},
		},
	}
	dm.SetAccount(ub)
	increments := Increments{
		&Increment{Cost: 2, BalanceInfo: &DebitInfo{
			Monetary: &MonetaryInfo{UUID: "moneya2"}, AccountID: ub.ID}},
	}
	cd := &CallDescriptor{ToR: utils.MetaVoice, Increments: increments}
	if _, err := cd.refundIncrements(nil); err != nil {
		t.Fatal(err)
	}
	ub, _ = dm.GetAccount(ub.ID)
	if ub.BalanceMap[utils.MetaMonetary][0].GetValue() != 100 || // no refund because of missing balance with ID
		len(ub.BalanceMap) != 1 ||
		len(ub.BalanceMap[utils.MetaMonetary]) != 1 {
		t.Error("Error refunding money: ", utils.ToIJSON(ub.BalanceMap))
	}
}
func TestValidateCallData(t *testing.T) {

	cd := &CallDescriptor{

		TimeStart: time.Date(2022, 07, 02, 20, 0, 0, 0, time.UTC),
		TimeEnd:   time.Date(2022, 07, 02, 20, 0, 0, 0, time.UTC),
	}
	if err := cd.ValidateCallData(); err == nil {
		t.Error(err)
	}
	cd.TimeEnd = time.Date(2022, 07, 02, 21, 0, 0, 0, time.UTC)
	cd.DurationIndex = 62 * time.Minute
	if err := cd.ValidateCallData(); err == nil {
		t.Error(err)
	}
	cd.DurationIndex = 60 * time.Minute
	if err = cd.ValidateCallData(); err != nil {
		t.Error(err)
	}
}

func TestCDRefundRounding(t *testing.T) {
	cfg := config.NewDefaultCGRConfig()
	cfg.GeneralCfg().LockingTimeout = 4 * time.Second
	config.SetCgrConfig(cfg)
	cd := &CallDescriptor{
		Category:    "call",
		Tenant:      "cgrates.org",
		Subject:     "1001",
		Account:     "1001",
		Destination: "1002",
		Increments: Increments{
			&Increment{
				BalanceInfo: &DebitInfo{
					AccountID: "acc_id",
					Unit:      &UnitInfo{},
				},
				Duration: 1 * time.Minute,
				Cost:     21,
			},
			&Increment{
				BalanceInfo: &DebitInfo{
					AccountID: "acc_id2",
					Unit:      &UnitInfo{},
				},
				Duration: 1 * time.Minute,
				Cost:     21,
			},
		},
	}

	dataDB, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
	if dErr != nil {
		t.Error(dErr)
	}
	dm := NewDataManager(dataDB, cfg.CacheCfg(), nil)
	fltrs := NewFilterS(cfg, nil, dm)

	if val, err := cd.RefundRounding(fltrs); err != nil {
		t.Errorf("received <%v>", err)
	} else if val != nil {
		t.Errorf("received %v", val)
	}
}

func TestCdAddRatingInfos(t *testing.T) {

	cd := &CallDescriptor{
		TimeStart: time.Date(2022, 12, 5, 0, 0, 0, 0, time.UTC),
		RatingInfos: RatingInfos{
			&RatingInfo{
				ActivationTime: time.Date(2022, 12, 4, 0, 0, 0, 0, time.UTC),
			},
		},
	}
	riS := RatingInfos{
		{
			ActivationTime: time.Date(2022, 12, 4, 0, 0, 0, 0, time.UTC),
		},
	}
	if has := cd.addRatingInfos(riS); !has {
		t.Error("expected true,received false")
	}

}

func TestCDRefundIncrementWarning(t *testing.T) {
	utils.Logger.SetLogLevel(4)
	utils.Logger.SetSyslog(nil)
	buf := new(bytes.Buffer)
	log.SetOutput(buf)
	defer func() {
		utils.Logger.SetLogLevel(0)
		log.SetOutput(os.Stderr)

	}()
	increments := Increments{
		&Increment{Cost: 2, BalanceInfo: &DebitInfo{
			Monetary: &MonetaryInfo{UUID: "moneya2"}, AccountID: "acc"}},
	}
	cd := &CallDescriptor{ToR: utils.MetaVoice, Increments: increments}
	expLog := `Could not get the account to be refunded`
	if _, err := cd.refundIncrements(nil); err != nil {
		t.Error(err)
	} else if rcvLog := buf.String(); !strings.Contains(rcvLog, expLog) {
		t.Errorf("Logger %v doesn't contain %v", rcvLog, expLog)
	}
}

func TestCallDescGetRatingPlansForPrefix(t *testing.T) {
	cfg := config.NewDefaultCGRConfig()
	tmpDm := dm
	defer func() {
		dm = tmpDm
	}()
	db, dErr := NewInternalDB(nil, nil, true, nil, cfg.DataDbCfg().Items)
	if dErr != nil {
		t.Error(dErr)
	}
	dm := NewDataManager(db, cfg.CacheCfg(), nil)
	cd := &CallDescriptor{
		Category:      "call",
		Tenant:        "cgrates.org",
		Subject:       "1005",
		Account:       "1005",
		Destination:   "1004",
		TimeStart:     time.Date(2022, time.January, 7, 16, 60, 0, 0, time.UTC),
		TimeEnd:       time.Date(2022, time.January, 7, 16, 60, 0, 0, time.UTC),
		LoopIndex:     1,
		ToR:           utils.MetaVoice,
		RunID:         utils.MetaRaw,
		DurationIndex: time.Second,
	}
	dm.SetRatingProfile(&RatingProfile{
		Id: "*out:cgrates.org:call:1005",
		RatingPlanActivations: RatingPlanActivations{&RatingPlanActivation{
			ActivationTime: time.Date(2015, 01, 01, 8, 0, 0, 0, time.UTC),
			RatingPlanId:   "RP_2CNT",
		}},
	})
	dm.SetRatingPlan(&RatingPlan{
		Id: "RP_2CNT",
		Timings: map[string]*RITiming{
			"30eab300": {
				Years:     utils.Years{},
				Months:    utils.Months{},
				MonthDays: utils.MonthDays{},
				WeekDays:  utils.WeekDays{},
				StartTime: "00:00:00",
			},
		},
		Ratings: map[string]*RIRate{
			"b457f86d": {
				Rates: []*RGRate{
					{
						GroupIntervalStart: 0,
						Value:              0,
						RateIncrement:      60 * time.Second,
						RateUnit:           60 * time.Second,
					},
				},
				RoundingMethod:   utils.MetaRoundingMiddle,
				RoundingDecimals: 4,
			},
		},
		DestinationRates: map[string]RPRateList{
			"DEST": []*RPRate{
				{
					Timing: "30eab300",
					Rating: "b457f86d",
					Weight: 10,
				},
			},
		},
	})
	dm.SetReverseDestination("DEST", []string{"1004"}, "")
	SetDataStorage(dm)
	if _, err = cd.getRatingPlansForPrefix(cd.GetKey(cd.Subject), 1); err != nil {
		t.Error(err)
	}
}

func TestEngineCallDescriptorString(t *testing.T) {
	TestCallDescriptor := &CallDescriptor{
		Category:            "call",
		Tenant:              "cgrates.org",
		Subject:             "user",
		Account:             "testAccount",
		Destination:         "local",
		TimeStart:           time.Date(2022, time.January, 7, 16, 60, 0, 0, time.UTC),
		TimeEnd:             time.Date(2022, time.January, 7, 16, 60, 0, 0, time.UTC),
		LoopIndex:           1.0,
		DurationIndex:       time.Minute * 3,
		FallbackSubject:     "testfallbacksubject",
		ToR:                 utils.MetaVoice,
		ExtraFields:         map[string]string{"key1": "value1"},
		MaxRate:             10.0,
		MaxRateUnit:         time.Minute,
		MaxCostSoFar:        0.5,
		CgrID:               "grid1",
		RunID:               "runID123",
		ForceDuration:       false,
		PerformRounding:     true,
		DenyNegativeAccount: true,
		DryRun:              false,
	}
	want, err := json.Marshal(TestCallDescriptor)
	if err != nil {
		t.Errorf("Error marshalling CallDescriptor to JSON: %v", err)
	}
	got := TestCallDescriptor.String()
	if got != string(want) {
		t.Errorf("Expected CallDescriptor.String() to return %s, got %s", want, got)
	}
}

func TestCallDescriptorClone(t *testing.T) {
	originalCD := &CallDescriptor{
		Category:            "call",
		Tenant:              "cgrates.org",
		Subject:             "user",
		Account:             "testAccount",
		Destination:         "local",
		TimeStart:           time.Date(2022, time.January, 7, 16, 60, 0, 0, time.UTC),
		TimeEnd:             time.Date(2022, time.January, 7, 16, 60, 0, 0, time.UTC),
		LoopIndex:           1,
		DurationIndex:       time.Minute * 3,
		FallbackSubject:     "testfallbacksubject",
		ToR:                 utils.MetaVoice,
		ExtraFields:         map[string]string{"key1": "value1"},
		MaxRate:             10.0,
		MaxRateUnit:         time.Minute,
		MaxCostSoFar:        0.5,
		CgrID:               "grid1",
		RunID:               "runID123",
		ForceDuration:       false,
		PerformRounding:     true,
		DenyNegativeAccount: true,
		DryRun:              false,
	}
	clonedCD := originalCD.Clone()
	if originalCD == clonedCD {
		t.Errorf("Expected cloned CallDescriptor to be a new instance, got pointer to original")
	}
}

func TestCallDescriptorgetgetRatingPlansForPrefix(t *testing.T) {
	Cache.Clear(nil)
	cd := CallDescriptor{
		Category:      "sms",
		Tenant:        "cgrates.org",
		Subject:       "testAccITAddBalanceWithDestinations",
		Account:       "testAccITAddBalanceWithDestinations",
		Destination:   "1003",
		DurationIndex: 0,
	}
	// Default FallbackDepth is 3
	got, err := cd.getRatingPlansForPrefix("testkey", 4)
	if err != utils.ErrMaxRecursionDepth {
		t.Errorf("getRatingPlansForPrefix() expected %v, got %v, ", utils.ErrMaxRecursionDepth, err)
	} else if got != 4 {
		t.Errorf("getRatingPlansForPrefix() expected %v, got %v, ", 4, got)
	}
	Cache.Clear(nil)
}

func TestAddRatingInfos(t *testing.T) {
	tests := []struct {
		existingRating RatingInfos
		newRating      RatingInfos
		expected       RatingInfos
	}{
		{
			existingRating: RatingInfos{
				&RatingInfo{ActivationTime: time.Date(2025, 4, 3, 9, 0, 0, 0, time.UTC), RateIntervals: RateIntervalList{&RateInterval{}}},
			},
			newRating: RatingInfos{
				&RatingInfo{ActivationTime: time.Date(2025, 4, 3, 8, 30, 0, 0, time.UTC), RateIntervals: RateIntervalList{&RateInterval{}}},
			},
			expected: RatingInfos{
				&RatingInfo{ActivationTime: time.Date(2025, 4, 3, 8, 30, 0, 0, time.UTC), RateIntervals: RateIntervalList{&RateInterval{}}},
				&RatingInfo{ActivationTime: time.Date(2025, 4, 3, 9, 0, 0, 0, time.UTC), RateIntervals: RateIntervalList{&RateInterval{}}},
			},
		},
	}

	for _, tt := range tests {
		t.Run("Add Rating Infos", func(t *testing.T) {
			cd := &CallDescriptor{
				RatingInfos: tt.existingRating,
			}

			cd.addRatingInfos(tt.newRating)

			if len(cd.RatingInfos) != len(tt.expected) {
				t.Errorf("Expected %d RatingInfos, got %d", len(tt.expected), len(cd.RatingInfos))
			}

			for i, ri := range cd.RatingInfos {
				if !ri.ActivationTime.Equal(tt.expected[i].ActivationTime) {
					t.Errorf("Expected activation time %v, got %v", tt.expected[i].ActivationTime, ri.ActivationTime)
				}
				if !reflect.DeepEqual(ri.RateIntervals, tt.expected[i].RateIntervals) {
					t.Errorf("Expected RateIntervals %v, got %v", tt.expected[i].RateIntervals, ri.RateIntervals)
				}
			}
		})
	}
}
