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

import (
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/cgrates/cron"
	"github.com/ericlagergren/decimal"
)

// RateProfile represents the configuration of a Rate profile
type RateProfile struct {
	Tenant          string
	ID              string
	FilterIDs       []string
	Weights         DynamicWeights
	MinCost         *Decimal
	MaxCost         *Decimal
	MaxCostStrategy string
	Rates           map[string]*Rate
}

// Clone clones *RateProfile
func (rp *RateProfile) Clone() *RateProfile {
	if rp == nil {
		return nil
	}
	cloned := &RateProfile{
		Tenant:          rp.Tenant,
		ID:              rp.ID,
		MaxCostStrategy: rp.MaxCostStrategy,
	}
	if rp.FilterIDs != nil {
		cloned.FilterIDs = make([]string, len(rp.FilterIDs))
		copy(cloned.FilterIDs, rp.FilterIDs)
	}
	if rp.MinCost != nil {
		cloned.MinCost = rp.MinCost.Clone()
	}
	if rp.MaxCost != nil {
		cloned.MaxCost = rp.MaxCost.Clone()
	}
	if rp.Weights != nil {
		cloned.Weights = rp.Weights.Clone()
	}
	if rp.Rates != nil {
		cloned.Rates = make(map[string]*Rate)
		for k, v := range rp.Rates {
			if v != nil {
				cloned.Rates[k] = v.Clone()
			}
		}
	}
	return cloned
}

// CacheClone returns a clone of RateProfile used by ltcache CacheCloner
func (rp *RateProfile) CacheClone() any {
	return rp.Clone()
}

func (rp *RateProfile) TenantID() string {
	return ConcatenatedKey(rp.Tenant, rp.ID)
}

func (rp *RateProfile) Compile() (err error) {
	for _, rtP := range rp.Rates {
		rtP.uID = ConcatenatedKey(rp.Tenant, rp.ID, rtP.ID)
		if err = rtP.Compile(); err != nil {
			return
		}
	}
	return
}

// Rate defines rate related information used within a RateProfile
type Rate struct {
	ID              string         // RateID
	FilterIDs       []string       // RateFilterIDs
	ActivationTimes string         // ActivationTimes is a cron formatted time interval
	Weights         DynamicWeights // RateWeights will decide the winner per interval start
	Blocker         bool           // RateBlocker will make this rate recurrent, deactivating further intervals
	IntervalRates   []*IntervalRate

	sched cron.Schedule // compiled version of activation times as cron.Schedule interface
	uID   string
}

// Clone returns a copy of rt
func (rt *Rate) Clone() *Rate {
	if rt == nil {
		return nil
	}
	cln := &Rate{
		ID:              rt.ID,
		ActivationTimes: rt.ActivationTimes,
		Blocker:         rt.Blocker,
		uID:             rt.uID,
		sched:           rt.sched,
	}
	if rt.FilterIDs != nil {
		cln.FilterIDs = make([]string, len(rt.FilterIDs))
		copy(cln.FilterIDs, rt.FilterIDs)
	}
	if rt.Weights != nil {
		cln.Weights = rt.Weights.Clone()
	}
	if rt.IntervalRates != nil {
		cln.IntervalRates = make([]*IntervalRate, len(rt.IntervalRates))
		for i, value := range rt.IntervalRates {
			cln.IntervalRates[i] = value.Clone()
		}
	}
	return cln
}

// UID returns system wide unique identifier
func (rt *Rate) UID() string {
	return rt.uID
}

type IntervalRate struct {
	IntervalStart *Decimal // Starting point when the Rate kicks in
	FixedFee      *Decimal
	RecurrentFee  *Decimal
	Unit          *Decimal // RateUnit
	Increment     *Decimal // RateIncrement
}

// Clone returns a copy of iR
func (iR *IntervalRate) Clone() *IntervalRate {
	cln := new(IntervalRate)
	if iR.IntervalStart != nil {
		cln.IntervalStart = iR.IntervalStart.Clone()
	}
	if iR.FixedFee != nil {
		cln.FixedFee = iR.FixedFee.Clone()
	}
	if iR.RecurrentFee != nil {
		cln.RecurrentFee = iR.RecurrentFee.Clone()
	}
	if iR.Unit != nil {
		cln.Unit = iR.Unit.Clone()
	}
	if iR.Increment != nil {
		cln.Increment = iR.Increment.Clone()
	}
	return cln
}

// Equals returns the equality between two IntervalRate
func (iR *IntervalRate) Equals(inRt *IntervalRate) (eq bool) {
	if iR == nil && inRt == nil {
		return true
	}
	if iR == nil && inRt != nil ||
		iR != nil && inRt == nil {
		return
	}

	return !(iR.RecurrentFee == nil && inRt.RecurrentFee != nil ||
		iR.RecurrentFee != nil && inRt.RecurrentFee == nil ||
		iR.FixedFee == nil && inRt.FixedFee != nil ||
		iR.FixedFee != nil && inRt.FixedFee == nil ||
		iR.Increment == nil && inRt.Increment != nil ||
		iR.Increment != nil && inRt.Increment == nil ||
		iR.Unit == nil && inRt.Unit != nil ||
		iR.Unit != nil && inRt.Unit == nil ||
		iR.IntervalStart == nil && inRt.IntervalStart != nil ||
		iR.IntervalStart != nil && inRt.IntervalStart == nil ||
		iR.RecurrentFee != nil && inRt.RecurrentFee != nil &&
			iR.RecurrentFee.Compare(inRt.RecurrentFee) != 0 ||
		iR.FixedFee != nil && inRt.FixedFee != nil &&
			iR.FixedFee.Compare(inRt.FixedFee) != 0 ||
		iR.Increment != nil && inRt.Increment != nil &&
			iR.Increment.Compare(inRt.Increment) != 0 ||
		iR.Unit != nil && inRt.Unit != nil &&
			iR.Unit.Compare(inRt.Unit) != 0 ||
		iR.IntervalStart != nil && inRt.IntervalStart != nil &&
			iR.IntervalStart.Compare(inRt.IntervalStart) != 0)
}

func (rt *Rate) Compile() (err error) {
	aTime := rt.ActivationTimes
	if aTime == EmptyString {
		aTime = "* * * * *"
	}
	if rt.sched, err = cron.ParseStandard(aTime); err != nil {
		return
	}
	return
}

// RunTimes returns the set of activation and deactivation times for this rate on the interval between >=sTime and <eTime
// aTimes is in the form of [][]
func (rt *Rate) RunTimes(sTime, eTime time.Time, verbosity int) (aTimes [][]time.Time, err error) {
	sTime = sTime.Add(-time.Minute) // to make sure we can cover startTime
	for i := 0; i < verbosity; i++ {
		aTime := rt.sched.Next(sTime)
		if aTime.IsZero() || !aTime.Before(eTime) { // #TestMe
			return
		}
		iTime := rt.sched.NextInactive(aTime)
		aTimes = append(aTimes, []time.Time{aTime, iTime})
		if iTime.IsZero() || !eTime.After(iTime) { // #TestMe
			return
		}
		sTime = iTime
	}
	// protect from memory leak
	Logger.Warning(
		fmt.Sprintf(
			"maximum runTime iterations reached for Rate: <%+v>, sTime: <%+v>, eTime: <%+v>",
			rt, sTime, eTime))
	return nil, ErrMaxIterationsReached
}

// RateProfileWithAPIOpts is used in replicatorV1 for dispatcher
type RateProfileWithAPIOpts struct {
	*RateProfile
	APIOpts map[string]any
}

// RateSInterval is used by RateS to integrate Rate info for one charging interval
type RateSInterval struct {
	IntervalStart  *Decimal
	Increments     []*RateSIncrement
	CompressFactor int64

	cost *decimal.Big // unexported total interval cost
}

// Clone returns a copy of rI
func (rI *RateSInterval) Clone() *RateSInterval {
	cln := &RateSInterval{
		CompressFactor: rI.CompressFactor,
	}
	if rI.IntervalStart != nil {
		cln.IntervalStart = rI.IntervalStart.Clone()
	}
	if rI.Increments != nil {
		cln.Increments = make([]*RateSIncrement, len(rI.Increments))
		for i, value := range rI.Increments {
			cln.Increments[i] = value.Clone()
		}
	}
	if rI.cost != nil {
		tmp := &decimal.Big{}
		cln.cost = tmp.Copy(rI.cost)
	}
	return cln
}

// AsRatesIntervalsCost converts RateSInterval to RateSIntervalCost
// The difference between this 2 is that RateSIntervalCost don't need IntervalStart
func (rI *RateSInterval) AsRatesIntervalsCost() (rIc *RateSIntervalCost) {
	rIc = &RateSIntervalCost{
		CompressFactor: rI.CompressFactor,
	}
	if rI.Increments != nil {
		rIc.Increments = make([]*RateSIncrementCost, len(rI.Increments))
		for idx, incr := range rI.Increments {
			rIc.Increments[idx] = incr.AsRateSIncrementCost()
		}
	}
	return
}

func (rI *RateSInterval) FieldAsInterface(fldPath []string) (any, error) {
	switch fldPath[0] {
	case IntervalStart:
		if len(fldPath) != 1 {
			return nil, ErrNotFound
		}
		return rI.IntervalStart, nil
	case CompressFactor:
		if len(fldPath) != 1 {
			return nil, ErrNotFound
		}
		return rI.CompressFactor, nil
	case Increments:
		if len(fldPath) != 1 {
			return nil, ErrNotFound
		}
		return rI.Increments, nil
	}

	opath, indx := GetPathIndex(fldPath[0])
	if opath != Increments {
		return nil, fmt.Errorf("unsupported field prefix: <%s>", opath)
	}
	if indx == nil {
		return nil, fmt.Errorf("invalid index for '%s' field", JoinedCharge)
	}
	if len(rI.Increments) <= *indx {
		return nil, ErrNotFound
	}
	incr := rI.Increments[*indx]
	if len(fldPath) == 1 {
		return incr, nil
	}
	return incr.FieldAsInterface(fldPath[1:])
}

type RateSIncrement struct {
	IncrementStart    *Decimal
	RateIntervalIndex int
	RateID            string
	CompressFactor    int64
	Usage             *Decimal

	cost *decimal.Big // unexported total increment cost
}

// Clone returns a copy of rI
func (rI *RateSIncrement) Clone() *RateSIncrement {
	cln := &RateSIncrement{
		RateIntervalIndex: rI.RateIntervalIndex,
		RateID:            rI.RateID,
		CompressFactor:    rI.CompressFactor,
	}
	if rI.IncrementStart != nil {
		cln.IncrementStart = rI.IncrementStart.Clone()
	}
	if rI.Usage != nil {
		cln.Usage = rI.Usage.Clone()
	}
	if rI.cost != nil {
		tmp := &decimal.Big{}
		cln.cost = tmp.Copy(rI.cost)
	}
	return cln
}

// Equals compares two RateSIntervals
func (rIl *RateSInterval) Equals(nRil *RateSInterval, rIlRef, nRilRef map[string]*IntervalRate) (eq bool) {
	if rIl == nil && nRil == nil {
		return true
	}
	if rIl.IntervalStart == nil && nRil.IntervalStart != nil ||
		rIl.IntervalStart != nil && nRil.IntervalStart == nil ||
		rIl.IntervalStart != nil && nRil.IntervalStart != nil &&
			rIl.IntervalStart.Compare(nRil.IntervalStart) != 0 ||
		(rIl.Increments != nil && rIl.Increments == nil ||
			rIl.Increments == nil && nRil.Increments != nil ||
			len(rIl.Increments) != len(nRil.Increments)) ||
		rIl.CompressFactor != nRil.CompressFactor {
		return
	}
	if rIl.Increments != nil && nRil.Increments != nil {
		for i, rtIn := range rIl.Increments {
			if !rtIn.Equals(nRil.Increments[i], rIlRef, nRilRef) {
				return
			}
		}
	}
	return true
}

// Equals returns the equality between two RateSIncrement
func (rI *RateSIncrement) Equals(rtIn *RateSIncrement, rIRef, rtInRef map[string]*IntervalRate) (eq bool) {
	return !(rI.Usage == nil && rtIn.Usage != nil ||
		rI.Usage != nil && rtIn.Usage == nil ||
		rI.Usage != nil && rtIn.Usage != nil &&
			rI.Usage.Compare(rtIn.Usage) != 0 ||
		(rI.IncrementStart == nil && rtIn.IncrementStart != nil ||
			rI.IncrementStart != nil && rtIn.IncrementStart == nil ||
			rI.IncrementStart != nil && rtIn.IncrementStart != nil &&
				rI.IncrementStart.Compare(rtIn.IncrementStart) != 0) ||
		rI.CompressFactor != rtIn.CompressFactor ||
		rI.RateIntervalIndex != rtIn.RateIntervalIndex ||
		rIRef != nil && rtInRef != nil &&
			rI.RateID != EmptyString && rtIn.RateID != EmptyString &&
			!rIRef[rI.RateID].Equals(rtInRef[rtIn.RateID]))
}

func (rI *RateSIncrement) FieldAsInterface(fldPath []string) (_ any, err error) {
	if len(fldPath) != 1 {
		return nil, ErrNotFound
	}
	switch fldPath[0] {
	case IncrementStart:
		return rI.IncrementStart, nil
	case RateIntervalIndex:
		return rI.RateIntervalIndex, nil
	case RateID:
		return rI.RateID, nil
	case CompressFactor:
		return rI.CompressFactor, nil
	case Usage:
		return rI.Usage, nil
	}
	return nil, fmt.Errorf("unsupported field prefix: <%s>", fldPath[0])
}

// RateProfileCost is the cost returned by RateS at cost queries
type RateProfileCost struct {
	ID              string // RateProfileID
	Cost            *Decimal
	MinCost         *Decimal
	MaxCost         *Decimal
	MaxCostStrategy string
	CostIntervals   []*RateSIntervalCost
	Rates           map[string]*IntervalRate
	Altered         []string
}

// RateSIntervalCost is used in the RateProfileCost to reflect the RateSInterval used
type RateSIntervalCost struct {
	Increments     []*RateSIncrementCost
	CompressFactor int64
}

// RateSIncrementCost is used in the RateProfileCost to reflect RateSIncrement
type RateSIncrementCost struct {
	Usage             *Decimal
	RateID            string
	RateIntervalIndex int
	CompressFactor    int64
}

// AsRateSIncrementCost converts RateSIncrement to RateSIncrementCost
// The difference between this 2 is that RateSIncrementCost don't need IncrementStart
func (rI *RateSIncrement) AsRateSIncrementCost() (rIc *RateSIncrementCost) {
	rIc = &RateSIncrementCost{
		RateIntervalIndex: rI.RateIntervalIndex,
		CompressFactor:    rI.CompressFactor,
		RateID:            rI.RateID,
	}
	if rI.Usage != nil {
		rIc.Usage = rI.Usage
	}
	return

}

// Equals returns the equality between two RateSIntervalCost
func (rIC *RateSIntervalCost) Equals(nRIc *RateSIntervalCost, rIlRef, nRilRef map[string]*IntervalRate) (eq bool) {
	if rIC.Increments != nil && nRIc.Increments == nil ||
		rIC.Increments == nil && nRIc.Increments != nil ||
		len(rIC.Increments) != len(nRIc.Increments) || rIC.CompressFactor != nRIc.CompressFactor {
		return
	}
	if rIC.Increments != nil && nRIc.Increments != nil {
		for i, rtIn := range rIC.Increments {
			if !rtIn.Equals(nRIc.Increments[i], rIlRef, nRilRef) {
				return
			}
		}
	}
	return true
}

// Equals returns the equality between two RateSIncrementCost
func (rIncrC *RateSIncrementCost) Equals(nRi *RateSIncrementCost, rIRef, rtInRef map[string]*IntervalRate) (eq bool) {
	return !(rIncrC.Usage == nil && nRi.Usage != nil ||
		rIncrC.Usage != nil && nRi.Usage == nil ||
		rIncrC.Usage != nil && nRi.Usage != nil &&
			rIncrC.Usage.Compare(nRi.Usage) != 0 ||
		rIncrC.CompressFactor != nRi.CompressFactor ||
		rIncrC.RateIntervalIndex != nRi.RateIntervalIndex ||
		rIRef == nil && rtInRef != nil ||
		rIRef != nil && rtInRef == nil ||
		rIRef != nil && rtInRef != nil &&
			!rIRef[rIncrC.RateID].Equals(rtInRef[nRi.RateID]))
}

/*
func (rpC *RateProfileCost) SynchronizeRateKeys(nRpCt *RateProfileCost) {
	rts := make(map[string]*IntervalRate)
	reverse := make(map[string]string)
	for key, val := range rpC.Rates {
		reverseKey := key
		for newKey, newVal := range nRpCt.Rates {
			if val.Equals(newVal) {
				reverseKey = newKey
				break
			}
		}
		rts[reverseKey] = val
		reverse[key] = reverseKey
	}
	rpC.Rates = rts
	for _, val := range rpC.RateSIntervals {
		for _, incrVal := range val.Increments {
			incrVal.RateID = reverse[incrVal.RateID]
		}
	}
}

*/

// Equals returns the equality between two RateProfileCost
func (rpC *RateProfileCost) Equals(nRpCt *RateProfileCost) (eq bool) {
	if rpC.ID != nRpCt.ID ||
		rpC.Cost == nil && nRpCt.Cost != nil ||
		rpC.Cost != nil && nRpCt.Cost == nil ||
		rpC.Cost != nil && nRpCt.Cost != nil &&
			rpC.Cost.Compare(nRpCt.Cost) != 0 ||
		rpC.MinCost == nil && nRpCt.MinCost != nil ||
		rpC.MinCost != nil && nRpCt.MinCost == nil ||
		rpC.MinCost != nil && nRpCt.MinCost != nil &&
			rpC.MinCost.Compare(nRpCt.MinCost) != 0 ||
		rpC.MaxCost == nil && nRpCt.MaxCost != nil ||
		rpC.MaxCost != nil && nRpCt.MaxCost == nil ||
		rpC.MaxCost != nil && nRpCt.MaxCost != nil &&
			rpC.MaxCost.Compare(nRpCt.MaxCost) != 0 ||
		rpC.MaxCostStrategy != nRpCt.MaxCostStrategy ||
		(rpC.CostIntervals != nil && nRpCt.CostIntervals == nil ||
			rpC.CostIntervals == nil && nRpCt.CostIntervals != nil ||
			len(rpC.CostIntervals) != len(nRpCt.CostIntervals)) ||
		(rpC.Rates != nil && nRpCt.Rates == nil ||
			rpC.Rates == nil && nRpCt.Rates != nil ||
			len(rpC.Rates) != len(nRpCt.Rates)) ||
		(rpC.Altered != nil && nRpCt.Altered == nil ||
			rpC.Altered == nil && nRpCt.Altered != nil ||
			len(rpC.Altered) != len(nRpCt.Altered)) {
		return
	}
	for idx, val := range rpC.CostIntervals {
		if ok := val.Equals(nRpCt.CostIntervals[idx], rpC.Rates, nRpCt.Rates); !ok {
			return
		}
	}
	for idx, val := range rpC.Altered {
		if val != nRpCt.Altered[idx] {
			return
		}
	}
	return true
}

// CorrectCost should be called in final phase of cost calculation
// in order to apply further correction like Min/MaxCost or rounding
func (rPc *RateProfileCost) CorrectCost(rndDec *int, rndMtd string) {
	if rPc.MinCost != nil && rPc.Cost.Compare(rPc.MinCost) < 0 {
		rPc.Cost = rPc.MinCost
		rPc.Altered = append(rPc.Altered, MinCost)
	}
	if rPc.MaxCost != nil && rPc.Cost.Compare(rPc.MaxCost) > 0 {
		rPc.Cost = rPc.MaxCost
		rPc.Altered = append(rPc.Altered, MaxCost)
	}
	if rndDec != nil {
		rPc.Cost = rPc.Cost.Round(*rndDec)
		rPc.Altered = append(rPc.Altered, RoundingDecimals)
	}
}

// Sort will sort the IntervalRates from each Rate based on IntervalStart
func (rpp *RateProfile) Sort() {
	for _, rate := range rpp.Rates {
		sort.Slice(rate.IntervalRates, func(i, j int) bool {
			return rate.IntervalRates[i].IntervalStart.Compare(rate.IntervalRates[j].IntervalStart) == -1
		})
	}
}

// CompressEquals compares two RateSIntervals for Compress function
func (rIv *RateSInterval) CompressEquals(rIv2 *RateSInterval) (eq bool) {
	if len(rIv.Increments) != len(rIv2.Increments) {
		return
	}
	for i, rIcr := range rIv.Increments {
		if !rIcr.CompressEquals(rIv2.Increments[i]) {
			return
		}
	}
	return true
}

func (rIv *RateSInterval) Cost(rts map[string]*IntervalRate) (cost *decimal.Big) {
	if rIv.cost == nil {
		for _, incrm := range rIv.Increments {
			rIv.cost = SumBig(rIv.cost, incrm.Cost(rts))
		}
	}
	return rIv.cost
}

// CompressEquals compares two RateSIncrement for Compress function
func (rIcr *RateSIncrement) CompressEquals(rIcr2 *RateSIncrement) (eq bool) {
	return rIcr.RateID == rIcr2.RateID &&
		rIcr.RateIntervalIndex == rIcr2.RateIntervalIndex &&
		rIcr.Usage.Big.Cmp(rIcr2.Usage.Big) == 0
}

// Cost computes the Cost on RateSIncrement
func (rIcr *RateSIncrement) Cost(rts map[string]*IntervalRate) (cost *decimal.Big) {
	if rIcr.cost == nil {
		icrRt, has := rts[rIcr.RateID]
		if !has {
			// return nil, fmt.Errorf("Cannot get the IntervalRate with this RateID: %s", rIcr.RateID)
			return
		}
		if rIcr.Usage.Compare(NewDecimal(-1, 0)) == 0 { // FixedFee
			rIcr.cost = icrRt.FixedFee.Big
		} else {
			rIcr.cost = icrRt.RecurrentFee.Big
			if icrRt.Unit != icrRt.Increment {
				rIcr.cost = DivideBig(
					MultiplyBig(rIcr.cost, icrRt.Increment.Big),
					icrRt.Unit.Big)
			}
			if rIcr.CompressFactor != 1 {
				rIcr.cost = MultiplyBig(
					rIcr.cost,
					decimal.WithContext(DecimalContext).SetUint64(uint64(rIcr.CompressFactor)))
			}
		}
	}
	return rIcr.cost
}

// CompressIntervals will compress intervals which equal
// func CompressIntervals(rtIvls []*RateSInterval) {
// }

type APIRateProfile struct {
	*RateProfile
	APIOpts map[string]any
}

// AsRate converts APIRate to Rate
func (ext *APIRate) AsRate() (rate *Rate, err error) {
	rate = &Rate{
		ID:              ext.ID,
		FilterIDs:       ext.FilterIDs,
		ActivationTimes: ext.ActivationTimes,
		Blocker:         ext.Blocker,
	}
	if ext.Weights != EmptyString {
		if rate.Weights, err = NewDynamicWeightsFromString(ext.Weights, ";", "&"); err != nil {
			return nil, err
		}
	}
	if len(ext.IntervalRates) != 0 {
		rate.IntervalRates = make([]*IntervalRate, len(ext.IntervalRates))
		for i, iRate := range ext.IntervalRates {
			if rate.IntervalRates[i], err = iRate.AsIntervalRate(); err != nil {
				return
			}
		}
	}
	return
}

type APIRate struct {
	ID              string   // RateID
	FilterIDs       []string // RateFilterIDs
	ActivationTimes string   // ActivationTimes is a cron formatted time interval
	Weights         string   // RateWeights will decide the winner per interval start
	Blocker         bool     // RateBlocker will make this rate recurrent, deactivating further intervals
	IntervalRates   []*APIIntervalRate
}

// AsIntervalRate converts APIIntervalRate to IntervalRate
func (ext *APIIntervalRate) AsIntervalRate() (iRate *IntervalRate, err error) {
	iRate = new(IntervalRate)
	if iRate.IntervalStart, err = NewDecimalFromUsage(ext.IntervalStart); err != nil {
		return
	}
	if ext.FixedFee != nil {
		iRate.FixedFee = NewDecimalFromFloat64(*ext.FixedFee)
	}
	if ext.RecurrentFee != nil {
		iRate.RecurrentFee = NewDecimalFromFloat64(*ext.RecurrentFee)
	}
	if ext.Unit != nil {
		iRate.Unit = NewDecimalFromFloat64(*ext.Unit)
	}
	if ext.Increment != nil {
		iRate.Increment = NewDecimalFromFloat64(*ext.Increment)
	}
	return
}

type APIIntervalRate struct {
	IntervalStart string
	FixedFee      *float64
	RecurrentFee  *float64
	Unit          *float64 // RateUnit
	Increment     *float64 // RateIncrement
}

type RemoveRPrfRates struct {
	Tenant  string
	ID      string
	RateIDs []string
	APIOpts map[string]any
}

func (rp *RateProfile) Set(path []string, val any, newBranch bool) (err error) {
	if len(path) == 0 {
		return ErrWrongPath
	}
	var rtID string
	if len(path) != 1 && path[0] == Rates {
		rtID = path[1]
		path = path[1:]
	} else if strings.HasPrefix(path[0], Rates) &&
		path[0][5] == '[' && path[0][len(path[0])-1] == ']' {
		rtID = path[0][6 : len(path[0])-1]
	}
	if rtID != EmptyString {
		if _, has := rp.Rates[rtID]; !has {
			rp.Rates[rtID] = &Rate{
				ID: rtID,
			}
		}
		return rp.Rates[rtID].Set(path[1:], val, newBranch)
	}
	if len(path) != 1 {
		return ErrWrongPath
	}
	switch path[0] {
	default:
		return ErrWrongPath
	case Tenant:
		rp.Tenant = IfaceAsString(val)
	case ID:
		rp.ID = IfaceAsString(val)
	case FilterIDs:
		var valA []string
		valA, err = IfaceAsStringSlice(val)
		rp.FilterIDs = append(rp.FilterIDs, valA...)
	case Weights:
		if val != EmptyString {
			rp.Weights, err = NewDynamicWeightsFromString(IfaceAsString(val), InfieldSep, ANDSep)
		}
	case MinCost:
		if val != EmptyString {
			var valB *decimal.Big
			valB, err = IfaceAsBig(val)
			rp.MinCost = &Decimal{valB}
		}
	case MaxCost:
		if val != EmptyString {
			var valB *decimal.Big
			valB, err = IfaceAsBig(val)
			rp.MaxCost = &Decimal{valB}
		}
	case MaxCostStrategy:
		rp.MaxCostStrategy = IfaceAsString(val)
	}
	return

}

func (rt *Rate) Set(path []string, val any, newBranch bool) (err error) {
	switch len(path) {
	default:
		return ErrWrongPath
	case 1:
		switch path[0] {
		default:
			return ErrWrongPath
		case ID:
			rt.ID = IfaceAsString(val)
		case FilterIDs:
			var valA []string
			valA, err = IfaceAsStringSlice(val)
			rt.FilterIDs = append(rt.FilterIDs, valA...)
		case Weights:
			if val != EmptyString {
				rt.Weights, err = NewDynamicWeightsFromString(IfaceAsString(val), InfieldSep, ANDSep)
			}
		case ActivationTimes:
			rt.ActivationTimes = IfaceAsString(val)
		case Blocker:
			rt.Blocker, err = IfaceAsBool(val)
		}
	case 2:
		if path[0] != IntervalRates {
			return ErrWrongPath
		}
		if len(rt.IntervalRates) == 0 || newBranch {
			rt.IntervalRates = append(rt.IntervalRates, &IntervalRate{IntervalStart: NewDecimal(0, 0), FixedFee: NewDecimal(0, 0)})
		}
		switch path[1] {
		case IntervalStart:
			var valB *decimal.Big
			valB, err = IfaceAsBig(val)
			rt.IntervalRates[len(rt.IntervalRates)-1].IntervalStart = &Decimal{valB}
		case FixedFee:
			var valB *decimal.Big
			valB, err = IfaceAsBig(val)
			rt.IntervalRates[len(rt.IntervalRates)-1].FixedFee = &Decimal{valB}
		case RecurrentFee:
			var valB *decimal.Big
			valB, err = IfaceAsBig(val)
			rt.IntervalRates[len(rt.IntervalRates)-1].RecurrentFee = &Decimal{valB}
		case Unit:
			var valB *decimal.Big
			valB, err = IfaceAsBig(val)
			rt.IntervalRates[len(rt.IntervalRates)-1].Unit = &Decimal{valB}
		case Increment:
			var valB *decimal.Big
			valB, err = IfaceAsBig(val)
			rt.IntervalRates[len(rt.IntervalRates)-1].Increment = &Decimal{valB}
		default:
			return ErrWrongPath
		}
	}
	return
}

func (rp *RateProfile) Merge(v2 any) {
	vi := v2.(*RateProfile)
	if len(vi.Tenant) != 0 {
		rp.Tenant = vi.Tenant
	}
	if len(vi.ID) != 0 {
		rp.ID = vi.ID
	}
	if len(vi.MaxCostStrategy) != 0 {
		rp.MaxCostStrategy = vi.MaxCostStrategy
	}
	rp.FilterIDs = append(rp.FilterIDs, vi.FilterIDs...)
	rp.Weights = append(rp.Weights, vi.Weights...)
	for k, v := range vi.Rates {
		rt, has := rp.Rates[k]
		if !has {
			rp.Rates[k] = v
			continue
		}
		rt.Merge(v)
	}
	o := decimal.New(0, 0)
	if vi.MinCost != nil && vi.MinCost.Cmp(o) != 0 {
		rp.MinCost = vi.MinCost
	}
	if vi.MaxCost != nil && vi.MinCost.Cmp(o) != 0 {
		rp.MaxCost = vi.MaxCost
	}
}

func (rt *Rate) Merge(vi *Rate) {
	if len(vi.ID) != 0 {
		rt.ID = vi.ID
	}
	if len(vi.ActivationTimes) != 0 {
		rt.ActivationTimes = vi.ActivationTimes
	}
	if vi.Blocker {
		rt.Blocker = vi.Blocker
	}
	rt.FilterIDs = append(rt.FilterIDs, vi.FilterIDs...)
	rt.Weights = append(rt.Weights, vi.Weights...)
	var equal bool
	for _, ivalRateV2 := range vi.IntervalRates {
		for _, ivalRate := range rt.IntervalRates {
			if ivalRate.IntervalStart.Compare(ivalRateV2.IntervalStart) == 0 {
				ivalRate.Merge(ivalRateV2)
				equal = true
				break
			}
		}
		if !equal {
			rt.IntervalRates = append(rt.IntervalRates, ivalRateV2)
		}
		equal = false
	}
}

func (ivalRate *IntervalRate) Merge(v2 *IntervalRate) {
	if v2.IntervalStart.Compare(NewDecimal(0, 0)) != 0 {
		ivalRate.IntervalStart = v2.IntervalStart
	}
	if v2.FixedFee.Compare(NewDecimal(0, 0)) != 0 {
		ivalRate.FixedFee = v2.FixedFee
	}
	if v2.RecurrentFee.Compare(NewDecimal(0, 0)) != 0 {
		ivalRate.RecurrentFee = v2.RecurrentFee
	}
	if v2.Unit.Compare(NewDecimal(0, 0)) != 0 {
		ivalRate.Unit = v2.Unit
	}
	if v2.Increment.Compare(NewDecimal(0, 0)) != 0 {
		ivalRate.Increment = v2.Increment
	}
}

func (rp *RateProfile) String() string { return ToJSON(rp) }
func (rp *RateProfile) FieldAsString(fldPath []string) (_ string, err error) {
	var val any
	if val, err = rp.FieldAsInterface(fldPath); err != nil {
		return
	}
	return IfaceAsString(val), nil
}
func (rp *RateProfile) FieldAsInterface(fldPath []string) (_ any, err error) {
	if len(fldPath) == 1 {
		switch fldPath[0] {
		default:
			fld, idxStr := GetPathIndexString(fldPath[0])
			if idxStr != nil {
				switch fld {
				case FilterIDs:
					var idx int
					if idx, err = strconv.Atoi(*idxStr); err != nil {
						return
					}
					if idx < len(rp.FilterIDs) {
						return rp.FilterIDs[idx], nil
					}
				case Rates:
					if rt, has := rp.Rates[*idxStr]; has {
						return rt, nil
					}
				}
			}
			return nil, ErrNotFound
		case Tenant:
			return rp.Tenant, nil
		case ID:
			return rp.ID, nil
		case FilterIDs:
			return rp.FilterIDs, nil
		case Weights:
			return rp.Weights.String(InfieldSep, ANDSep), nil
		case MinCost:
			return rp.MinCost, nil
		case MaxCost:
			return rp.MaxCost, nil
		case MaxCostStrategy:
			return rp.MaxCostStrategy, nil
		case Rates:
			return rp.Rates, nil
		}
	}
	if len(fldPath) == 0 {
		return nil, ErrNotFound
	}
	fld, idxStr := GetPathIndexString(fldPath[0])
	if fld != Rates {
		return nil, ErrNotFound
	}

	if idxStr == nil {

		idxStr = &fldPath[1]
		fldPath = fldPath[1:]
	}
	rt, has := rp.Rates[*idxStr]
	if !has {
		return nil, ErrNotFound
	}
	if len(fldPath) == 1 {
		return rt, nil
	}
	return rt.FieldAsInterface(fldPath[1:])
}

func (rt *Rate) String() string { return ToJSON(rt) }
func (rt *Rate) FieldAsString(fldPath []string) (_ string, err error) {
	var val any
	if val, err = rt.FieldAsInterface(fldPath); err != nil {
		return
	}
	return IfaceAsString(val), nil
}
func (rt *Rate) FieldAsInterface(fldPath []string) (_ any, err error) {
	switch len(fldPath) {
	default:
		return nil, ErrNotFound
	case 1:
		switch fldPath[0] {
		default:
			fld, idx := GetPathIndex(fldPath[0])
			if idx != nil {
				switch fld {
				case IntervalRates:
					if *idx < len(rt.IntervalRates) {
						return rt.IntervalRates[*idx], nil
					}
				case FilterIDs:
					if *idx < len(rt.FilterIDs) {
						return rt.FilterIDs[*idx], nil
					}
				}
			}
			return nil, ErrNotFound
		case ID:
			return rt.ID, nil
		case FilterIDs:
			return rt.FilterIDs, nil
		case Weights:
			return rt.Weights.String(InfieldSep, ANDSep), nil
		case IntervalRates:
			return rt.IntervalRates, nil
		case Blocker:
			return rt.Blocker, nil
		case ActivationTimes:
			return rt.ActivationTimes, nil
		}
	case 2:
		fld, idx := GetPathIndex(fldPath[0])
		if fld != IntervalRates ||
			idx == nil ||
			*idx >= len(rt.IntervalRates) {
			return nil, ErrNotFound
		}
		return rt.IntervalRates[*idx].FieldAsInterface(fldPath[1:])
	}
}

// AsMapStringInterface converts Rate struct to map[string]any
func (rt *Rate) AsMapStringInterface() map[string]any {
	if rt == nil {
		return nil
	}
	return map[string]any{
		ID:              rt.ID,
		FilterIDs:       rt.FilterIDs,
		ActivationTimes: rt.ActivationTimes,
		Weights:         rt.Weights,
		Blocker:         rt.Blocker,
		IntervalRates:   rt.IntervalRates,
	}
}

// MapStringInterfaceToRate converts map[string]any to Rate struct
func MapStringInterfaceToRate(m map[string]any) (*Rate, error) {
	rt := &Rate{}
	if v, ok := m[ID].(string); ok {
		rt.ID = v
	}
	rt.FilterIDs = InterfaceToStringSlice(m[FilterIDs])
	if v, ok := m[ActivationTimes].(string); ok {
		rt.ActivationTimes = v
	}
	rt.Weights = InterfaceToDynamicWeights(m[Weights])
	if v, ok := m[Blocker].(bool); ok {
		rt.Blocker = v
	}
	var err error
	if rt.IntervalRates, err = InterfaceToIntervalRates(m[IntervalRates]); err != nil {
		return nil, err
	}
	return rt, nil
}

func (iR *IntervalRate) String() string { return ToJSON(iR) }
func (iR *IntervalRate) FieldAsString(fldPath []string) (_ string, err error) {
	var val any
	if val, err = iR.FieldAsInterface(fldPath); err != nil {
		return
	}
	return IfaceAsString(val), nil
}
func (iR *IntervalRate) FieldAsInterface(fldPath []string) (_ any, err error) {
	if len(fldPath) != 1 {
		return nil, ErrNotFound
	}
	switch fldPath[0] {
	default:
		return nil, ErrNotFound
	case IntervalStart:
		return iR.IntervalStart, nil
	case FixedFee:
		return iR.FixedFee, nil
	case RecurrentFee:
		return iR.RecurrentFee, nil
	case Unit:
		return iR.Unit, nil
	case Increment:
		return iR.Increment, nil
	}
}

// InterfaceToIntervalRates converts any to []*IntervalRate
func InterfaceToIntervalRates(v any) (intervalRates []*IntervalRate, err error) {
	if v == nil {
		return
	}
	switch val := v.(type) {
	case []*IntervalRate:
		return val, nil
	case []any:
		result := make([]*IntervalRate, 0, len(val))
		for _, item := range val {
			if irMap, ok := item.(map[string]any); ok {
				ir := new(IntervalRate)
				if ir.IntervalStart, err = NewDecimalFromInterface(irMap[IntervalStart]); err != nil {
					return nil, err
				}
				if ir.FixedFee, err = NewDecimalFromInterface(irMap[FixedFee]); err != nil {
					return nil, err
				}
				if ir.RecurrentFee, err = NewDecimalFromInterface(irMap[RecurrentFee]); err != nil {
					return nil, err
				}
				if ir.Unit, err = NewDecimalFromInterface(irMap[Unit]); err != nil {
					return nil, err
				}
				if ir.Increment, err = NewDecimalFromInterface(irMap[Increment]); err != nil {
					return nil, err
				}
				result = append(result, ir)
			}
		}
		return result, nil
	}
	return
}

// AsDataDBMap is used to is a convert method in order to properly set trough a hasmap in redis server our rate profile
func (rp *RateProfile) AsDataDBMap(ms Marshaler) (mp map[string]any, err error) {
	mp = map[string]any{
		MaxCostStrategy: rp.MaxCostStrategy,
	}
	if len(rp.FilterIDs) != 0 {
		var fltrs string
		for idx, fltr := range rp.FilterIDs {
			fltrs += fltr
			if idx != len(rp.FilterIDs)-1 {
				fltrs += InfieldSep
			}
		}
		mp[FilterIDs] = fltrs
	}
	if rp.Weights != nil {
		mp[Weights] = rp.Weights.String(InfieldSep, ANDSep)
	}
	if rp.MinCost != nil {
		mp[MinCost] = rp.MinCost.String()
	}
	if rp.MaxCost != nil {
		mp[MaxCost] = rp.MaxCost.String()
	}
	for rateID, rt := range rp.Rates {
		var result []byte
		if result, err = ms.Marshal(rt); err != nil {
			return nil, err
		}
		fldKey := ConcatenatedKey(Rates, rateID)
		mp[fldKey] = string(result)
	}
	return mp, nil
}

// AsMapStringInterface converts RateProfile struct to map[string]any
//
//	! Rates not included !
func (rp *RateProfile) AsMapStringInterface() map[string]any {
	if rp == nil {
		return nil
	}
	return map[string]any{
		Tenant:          rp.Tenant,
		ID:              rp.ID,
		FilterIDs:       rp.FilterIDs,
		Weights:         rp.Weights,
		MinCost:         rp.MinCost,
		MaxCost:         rp.MaxCost,
		MaxCostStrategy: rp.MaxCostStrategy,
	}
}

// MapStringInterfaceToRateProfile converts map[string]any to RateProfile struct
//
//	! Rates not included !
func MapStringInterfaceToRateProfile(m map[string]any) (*RateProfile, error) {
	rp := &RateProfile{}
	if v, ok := m[Tenant].(string); ok {
		rp.Tenant = v
	}
	if v, ok := m[ID].(string); ok {
		rp.ID = v
	}
	rp.FilterIDs = InterfaceToStringSlice(m[FilterIDs])
	rp.Weights = InterfaceToDynamicWeights(m[Weights])
	var err error
	if rp.MinCost, err = NewDecimalFromInterface(m[MinCost]); err != nil {
		return nil, err
	}
	if rp.MaxCost, err = NewDecimalFromInterface(m[MaxCost]); err != nil {
		return nil, err
	}
	if v, ok := m[MaxCostStrategy].(string); ok {
		rp.MaxCostStrategy = v
	}
	rp.Rates = make(map[string]*Rate)
	return rp, nil
}

// NewRateProfileFromMapDataDBMap will convert a RateProfile map into a RatePRofile struct. This is used when we get the map from redis database
func NewRateProfileFromMapDataDBMap(tnt, id string, mapRP map[string]any, ms Marshaler) (rp *RateProfile, err error) {
	rp = &RateProfile{
		ID:              id,
		Tenant:          tnt,
		MaxCostStrategy: IfaceAsString(mapRP[MaxCostStrategy]),
		Rates:           make(map[string]*Rate),
	}
	if fltrsIDs, has := mapRP[FilterIDs]; has {
		fltrs := strings.Split(IfaceAsString(fltrsIDs), InfieldSep)
		rp.FilterIDs = make([]string, len(fltrs))
		copy(rp.FilterIDs, fltrs)
	}
	if weights, has := mapRP[Weights]; has {
		rp.Weights, err = NewDynamicWeightsFromString(IfaceAsString(weights), InfieldSep, ANDSep)
		if err != nil {
			return nil, err
		}
	}
	if minCost, has := mapRP[MinCost]; has {
		rp.MinCost, err = NewDecimalFromString(IfaceAsString(minCost))
		if err != nil {
			return nil, err
		}
	}
	if maxCost, has := mapRP[MaxCost]; has {
		rp.MaxCost, err = NewDecimalFromString(IfaceAsString(maxCost))
		if err != nil {
			return nil, err
		}
	}
	for keyID, rateStr := range mapRP {
		if strings.HasPrefix(keyID, Rates+ConcatenatedKeySep) {
			var rate *Rate
			if err := ms.Unmarshal([]byte(IfaceAsString(rateStr)), &rate); err != nil {
				return nil, err
			}
			rp.Rates[strings.TrimPrefix(keyID, Rates+ConcatenatedKeySep)] = rate
		}
	}
	return rp, err
}
