/*
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 (
	"fmt"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"time"

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

func csvLoad(s any, values []string) (any, error) {
	fieldValueMap := make(map[string]string)
	st := reflect.TypeOf(s)
	numFields := st.NumField()
	for i := 0; i < numFields; i++ {
		field := st.Field(i)
		re := field.Tag.Get("re")
		index := field.Tag.Get("index")
		if index != utils.EmptyString {
			idx, err := strconv.Atoi(index)
			if err != nil || len(values) <= idx {
				return nil, fmt.Errorf("invalid %v.%v index %v", st.Name(), field.Name, index)
			}
			if re != utils.EmptyString {
				if matched, err := regexp.MatchString(re, values[idx]); !matched || err != nil {
					return nil, fmt.Errorf("invalid %v.%v value %v", st.Name(), field.Name, values[idx])
				}
			}
			fieldValueMap[field.Name] = values[idx]
		}
	}
	elem := reflect.New(st).Elem()
	for fieldName, fieldValue := range fieldValueMap {
		field := elem.FieldByName(fieldName)
		if field.IsValid() {
			switch field.Kind() {
			case reflect.Float64:
				if fieldValue == utils.EmptyString {
					fieldValue = "0"
				}
				value, err := strconv.ParseFloat(fieldValue, 64)
				if err != nil {
					return nil, fmt.Errorf(`invalid value "%s" for field %s.%s`, fieldValue, st.Name(), fieldName)
				}
				field.SetFloat(value)
			case reflect.Int:
				if fieldValue == utils.EmptyString {
					fieldValue = "0"
				}
				value, err := strconv.Atoi(fieldValue)
				if err != nil {
					return nil, fmt.Errorf(`invalid value "%s" for field %s.%s`, fieldValue, st.Name(), fieldName)
				}
				field.SetInt(int64(value))
			case reflect.Bool:
				if fieldValue == utils.EmptyString {
					fieldValue = "false"
				}
				value, err := strconv.ParseBool(fieldValue)
				if err != nil {
					return nil, fmt.Errorf(`invalid value "%s" for field %s.%s`, fieldValue, st.Name(), fieldName)
				}
				field.SetBool(value)
			case reflect.String:
				field.SetString(fieldValue)
			}
		}
	}
	return elem.Interface(), nil
}

// CsvDump receive and interface and convert it to a slice of string
func CsvDump(s any) ([]string, error) {
	fieldIndexMap := make(map[string]int)
	st := reflect.ValueOf(s)
	if st.Kind() == reflect.Ptr {
		st = st.Elem()
		s = st.Interface()
	}
	numFields := st.NumField()
	stCopy := reflect.TypeOf(s)
	for i := 0; i < numFields; i++ {
		field := stCopy.Field(i)
		index := field.Tag.Get("index")
		if index != utils.EmptyString {
			if idx, err := strconv.Atoi(index); err != nil {
				return nil, fmt.Errorf("invalid %v.%v index %v", stCopy.Name(), field.Name, index)
			} else {
				fieldIndexMap[field.Name] = idx
			}
		}
	}
	result := make([]string, len(fieldIndexMap))
	for fieldName, fieldIndex := range fieldIndexMap {
		field := st.FieldByName(fieldName)
		if field.IsValid() && fieldIndex < len(result) {
			switch field.Kind() {
			case reflect.Float64:
				result[fieldIndex] = strconv.FormatFloat(field.Float(), 'f', -1, 64)
			case reflect.Int:
				result[fieldIndex] = strconv.FormatInt(field.Int(), 10)
			case reflect.Bool:
				result[fieldIndex] = strconv.FormatBool(field.Bool())
			case reflect.String:
				result[fieldIndex] = field.String()
			}
		}
	}
	return result, nil
}

func getColumnCount(s any) int {
	st := reflect.TypeOf(s)
	numFields := st.NumField()
	count := 0
	for i := 0; i < numFields; i++ {
		field := st.Field(i)
		index := field.Tag.Get("index")
		if index != utils.EmptyString {
			count++
		}
	}
	return count
}

type ResourceMdls []*ResourceMdl

// CSVHeader return the header for csv fields as a slice of string
func (tps ResourceMdls) CSVHeader() (result []string) {
	return []string{"#" + utils.Tenant, utils.ID, utils.FilterIDs, utils.Weights,
		utils.UsageTTL, utils.Limit, utils.AllocationMessage, utils.Blocker, utils.Stored,
		utils.ThresholdIDs}
}

func (tps ResourceMdls) AsTPResources() (result []*utils.TPResourceProfile) {
	mrl := make(map[string]*utils.TPResourceProfile)
	filterMap := make(map[string]utils.StringSet)
	thresholdMap := make(map[string]utils.StringSet)
	for _, tp := range tps {
		tenID := (&utils.TenantID{Tenant: tp.Tenant, ID: tp.ID}).TenantID()
		rl, found := mrl[tenID]
		if !found {
			rl = &utils.TPResourceProfile{
				TPid:    tp.Tpid,
				Tenant:  tp.Tenant,
				ID:      tp.ID,
				Blocker: tp.Blocker,
				Stored:  tp.Stored,
			}
		}
		if tp.UsageTTL != utils.EmptyString {
			rl.UsageTTL = tp.UsageTTL
		}
		if tp.Weights != "" {
			rl.Weights = tp.Weights
		}
		if tp.Limit != utils.EmptyString {
			rl.Limit = tp.Limit
		}
		if tp.AllocationMessage != utils.EmptyString {
			rl.AllocationMessage = tp.AllocationMessage
		}
		rl.Blocker = tp.Blocker
		rl.Stored = tp.Stored
		if tp.ThresholdIDs != utils.EmptyString {
			if _, has := thresholdMap[tenID]; !has {
				thresholdMap[tenID] = make(utils.StringSet)
			}
			thresholdMap[tenID].AddSlice(strings.Split(tp.ThresholdIDs, utils.InfieldSep))
		}
		if tp.FilterIDs != utils.EmptyString {
			if _, has := filterMap[tenID]; !has {
				filterMap[tenID] = make(utils.StringSet)
			}
			filterMap[tenID].AddSlice(strings.Split(tp.FilterIDs, utils.InfieldSep))
		}
		mrl[tenID] = rl
	}
	result = make([]*utils.TPResourceProfile, len(mrl))
	i := 0
	for tntID, rl := range mrl {
		result[i] = rl
		result[i].FilterIDs = filterMap[tntID].AsSlice()
		result[i].ThresholdIDs = thresholdMap[tntID].AsSlice()
		i++
	}
	return
}

func APItoModelResource(rl *utils.TPResourceProfile) (mdls ResourceMdls) {
	if rl == nil {
		return
	}
	// In case that TPResourceProfile don't have filter
	if len(rl.FilterIDs) == 0 {
		mdl := &ResourceMdl{
			Tpid:              rl.TPid,
			Tenant:            rl.Tenant,
			ID:                rl.ID,
			Blocker:           rl.Blocker,
			Stored:            rl.Stored,
			UsageTTL:          rl.UsageTTL,
			Weights:           rl.Weights,
			Limit:             rl.Limit,
			AllocationMessage: rl.AllocationMessage,
		}
		for i, val := range rl.ThresholdIDs {
			if i != 0 {
				mdl.ThresholdIDs += utils.InfieldSep
			}
			mdl.ThresholdIDs += val
		}
		mdls = append(mdls, mdl)
	}
	for i, fltr := range rl.FilterIDs {
		mdl := &ResourceMdl{
			Tpid:    rl.TPid,
			Tenant:  rl.Tenant,
			ID:      rl.ID,
			Blocker: rl.Blocker,
			Stored:  rl.Stored,
		}
		if i == 0 {
			mdl.UsageTTL = rl.UsageTTL
			mdl.Weights = rl.Weights
			mdl.Limit = rl.Limit
			mdl.AllocationMessage = rl.AllocationMessage
			for i, val := range rl.ThresholdIDs {
				if i != 0 {
					mdl.ThresholdIDs += utils.InfieldSep
				}
				mdl.ThresholdIDs += val
			}
		}
		mdl.FilterIDs = fltr
		mdls = append(mdls, mdl)
	}
	return
}

func APItoResource(tpRL *utils.TPResourceProfile, timezone string) (rp *utils.ResourceProfile, err error) {
	rp = &utils.ResourceProfile{
		Tenant:            tpRL.Tenant,
		ID:                tpRL.ID,
		Blocker:           tpRL.Blocker,
		Stored:            tpRL.Stored,
		AllocationMessage: tpRL.AllocationMessage,
		ThresholdIDs:      make([]string, len(tpRL.ThresholdIDs)),
		FilterIDs:         make([]string, len(tpRL.FilterIDs)),
	}
	if tpRL.Weights != utils.EmptyString {
		rp.Weights, err = utils.NewDynamicWeightsFromString(tpRL.Weights, utils.InfieldSep, utils.ANDSep)
		if err != nil {
			return
		}
	}
	if tpRL.UsageTTL != utils.EmptyString {
		if rp.UsageTTL, err = utils.ParseDurationWithNanosecs(tpRL.UsageTTL); err != nil {
			return nil, err
		}
	}
	copy(rp.FilterIDs, tpRL.FilterIDs)
	copy(rp.ThresholdIDs, tpRL.ThresholdIDs)
	if tpRL.Limit != utils.EmptyString {
		if rp.Limit, err = strconv.ParseFloat(tpRL.Limit, 64); err != nil {
			return nil, err
		}
	}
	return rp, nil
}

func ResourceProfileToAPI(rp *utils.ResourceProfile) (tpRL *utils.TPResourceProfile) {
	tpRL = &utils.TPResourceProfile{
		Tenant:            rp.Tenant,
		ID:                rp.ID,
		FilterIDs:         make([]string, len(rp.FilterIDs)),
		Limit:             strconv.FormatFloat(rp.Limit, 'f', -1, 64),
		AllocationMessage: rp.AllocationMessage,
		Blocker:           rp.Blocker,
		Stored:            rp.Stored,
		Weights:           rp.Weights.String(utils.InfieldSep, utils.ANDSep),
		ThresholdIDs:      make([]string, len(rp.ThresholdIDs)),
	}
	if rp.UsageTTL != time.Duration(0) {
		tpRL.UsageTTL = rp.UsageTTL.String()
	}
	copy(tpRL.FilterIDs, rp.FilterIDs)
	copy(tpRL.ThresholdIDs, rp.ThresholdIDs)
	return
}

type IPMdls []*IPMdl

// CSVHeader return the header for csv fields as a slice of string
func (tps IPMdls) CSVHeader() []string {
	return []string{
		"#" + utils.Tenant,
		utils.ID,
		utils.FilterIDs,
		utils.Weights,
		utils.TTL,
		utils.Stored,
		utils.PoolID,
		utils.PoolFilterIDs,
		utils.PoolType,
		utils.PoolRange,
		utils.PoolStrategy,
		utils.PoolMessage,
		utils.PoolWeights,
		utils.PoolBlockers,
	}
}

func (tps IPMdls) AsTPIPs() []*utils.TPIPProfile {
	filterMap := make(map[string]utils.StringSet)
	mst := make(map[string]*utils.TPIPProfile)
	poolMap := make(map[string]map[string]*utils.TPIPPool)
	for _, mdl := range tps {
		tenID := (&utils.TenantID{Tenant: mdl.Tenant, ID: mdl.ID}).TenantID()
		tpip, found := mst[tenID]
		if !found {
			tpip = &utils.TPIPProfile{
				TPid:   mdl.Tpid,
				Tenant: mdl.Tenant,
				ID:     mdl.ID,
				Stored: mdl.Stored,
			}
		}
		// Handle Pool
		if mdl.PoolID != utils.EmptyString {
			if _, has := poolMap[tenID]; !has {
				poolMap[tenID] = make(map[string]*utils.TPIPPool)
			}
			poolID := mdl.PoolID
			if mdl.PoolFilterIDs != utils.EmptyString {
				poolID = utils.ConcatenatedKey(poolID,
					utils.NewStringSet(strings.Split(mdl.PoolFilterIDs, utils.InfieldSep)).Sha1())
			}
			pool, found := poolMap[tenID][poolID]
			if !found {
				pool = &utils.TPIPPool{
					ID:       mdl.PoolID,
					Type:     mdl.PoolType,
					Range:    mdl.PoolRange,
					Strategy: mdl.PoolStrategy,
					Message:  mdl.PoolMessage,
					Weights:  mdl.PoolWeights,
					Blockers: mdl.PoolBlockers,
				}
			}
			if mdl.PoolFilterIDs != utils.EmptyString {
				poolFilterSplit := strings.Split(mdl.PoolFilterIDs, utils.InfieldSep)
				pool.FilterIDs = append(pool.FilterIDs, poolFilterSplit...)
			}
			poolMap[tenID][poolID] = pool
		}
		// Profile-level fields
		if mdl.TTL != utils.EmptyString {
			tpip.TTL = mdl.TTL
		}
		if mdl.Weights != "" {
			tpip.Weights = mdl.Weights
		}
		if mdl.Stored {
			tpip.Stored = mdl.Stored
		}

		if mdl.FilterIDs != utils.EmptyString {
			if _, has := filterMap[tenID]; !has {
				filterMap[tenID] = make(utils.StringSet)
			}
			filterMap[tenID].AddSlice(strings.Split(mdl.FilterIDs, utils.InfieldSep))
		}
		mst[tenID] = tpip
	}
	// Build result with Pools
	result := make([]*utils.TPIPProfile, len(mst))
	i := 0
	for tntID, tpip := range mst {
		result[i] = tpip
		for _, poolData := range poolMap[tntID] {
			result[i].Pools = append(result[i].Pools, poolData)
		}
		result[i].FilterIDs = filterMap[tntID].AsSlice()
		i++
	}
	return result
}

func APItoModelIP(tp *utils.TPIPProfile) IPMdls {
	if tp == nil {
		return nil
	}
	var mdls IPMdls
	// Handle case with no pools
	if len(tp.Pools) == 0 {
		mdl := &IPMdl{
			Tpid:    tp.TPid,
			Tenant:  tp.Tenant,
			ID:      tp.ID,
			TTL:     tp.TTL,
			Stored:  tp.Stored,
			Weights: tp.Weights,
		}
		for i, val := range tp.FilterIDs {
			if i != 0 {
				mdl.FilterIDs += utils.InfieldSep
			}
			mdl.FilterIDs += val
		}
		mdls = append(mdls, mdl)
		return mdls
	}
	for i, pool := range tp.Pools {
		mdl := &IPMdl{
			Tpid:   tp.TPid,
			Tenant: tp.Tenant,
			ID:     tp.ID,
			Stored: tp.Stored,
		}
		if i == 0 {
			// Profile-level fields only on first row
			mdl.TTL = tp.TTL
			mdl.Weights = tp.Weights
			for j, val := range tp.FilterIDs {
				if j != 0 {
					mdl.FilterIDs += utils.InfieldSep
				}
				mdl.FilterIDs += val
			}
		}
		// Pool fields on every row
		mdl.PoolID = pool.ID
		mdl.PoolType = pool.Type
		mdl.PoolRange = pool.Range
		mdl.PoolStrategy = pool.Strategy
		mdl.PoolMessage = pool.Message
		mdl.PoolWeights = pool.Weights
		mdl.PoolBlockers = pool.Blockers
		for j, val := range pool.FilterIDs {
			if j != 0 {
				mdl.PoolFilterIDs += utils.InfieldSep
			}
			mdl.PoolFilterIDs += val
		}
		mdls = append(mdls, mdl)
	}
	return mdls
}

func APItoIP(tp *utils.TPIPProfile) (*utils.IPProfile, error) {
	ipp := &utils.IPProfile{
		Tenant:    tp.Tenant,
		ID:        tp.ID,
		Stored:    tp.Stored,
		FilterIDs: make([]string, len(tp.FilterIDs)),
		Pools:     make([]*utils.IPPool, len(tp.Pools)),
	}
	if tp.Weights != utils.EmptyString {
		var err error
		ipp.Weights, err = utils.NewDynamicWeightsFromString(tp.Weights, utils.InfieldSep, utils.ANDSep)
		if err != nil {
			return nil, err
		}
	}
	if tp.TTL != utils.EmptyString {
		var err error
		if ipp.TTL, err = utils.ParseDurationWithNanosecs(tp.TTL); err != nil {
			return nil, err
		}
	}

	copy(ipp.FilterIDs, tp.FilterIDs)

	for i, pool := range tp.Pools {
		ipp.Pools[i] = &utils.IPPool{
			ID:        pool.ID,
			FilterIDs: pool.FilterIDs,
			Type:      pool.Type,
			Range:     pool.Range,
			Strategy:  pool.Strategy,
			Message:   pool.Message,
		}
		if pool.Weights != utils.EmptyString {
			var err error
			ipp.Pools[i].Weights, err = utils.NewDynamicWeightsFromString(pool.Weights, utils.InfieldSep, utils.ANDSep)
			if err != nil {
				return nil, err
			}
		}
		if pool.Blockers != utils.EmptyString {
			var err error
			ipp.Pools[i].Blockers, err = utils.NewDynamicBlockersFromString(pool.Blockers, utils.InfieldSep, utils.ANDSep)
			if err != nil {
				return nil, err
			}
		}
	}
	return ipp, nil
}

func IPProfileToAPI(ipp *utils.IPProfile) *utils.TPIPProfile {
	tp := &utils.TPIPProfile{
		Tenant:    ipp.Tenant,
		ID:        ipp.ID,
		FilterIDs: make([]string, len(ipp.FilterIDs)),
		Weights:   ipp.Weights.String(utils.InfieldSep, utils.ANDSep),
		Stored:    ipp.Stored,
		Pools:     make([]*utils.TPIPPool, len(ipp.Pools)),
	}
	if ipp.TTL != time.Duration(0) {
		tp.TTL = ipp.TTL.String()
	}

	copy(tp.FilterIDs, ipp.FilterIDs)

	for i, pool := range ipp.Pools {
		tp.Pools[i] = &utils.TPIPPool{
			ID:        pool.ID,
			FilterIDs: pool.FilterIDs,
			Type:      pool.Type,
			Range:     pool.Range,
			Blockers:  pool.Blockers.String(utils.InfieldSep, utils.ANDSep),
			Weights:   pool.Weights.String(utils.InfieldSep, utils.ANDSep),
			Strategy:  pool.Strategy,
			Message:   pool.Message,
		}
	}
	return tp
}

type StatMdls []*StatMdl

// CSVHeader return the header for csv fields as a slice of string
func (tps StatMdls) CSVHeader() (result []string) {
	return []string{"#" + utils.Tenant, utils.ID, utils.FilterIDs, utils.Weights, utils.Blockers, utils.QueueLength, utils.TTL, utils.MinItems, utils.Stored, utils.ThresholdIDs, utils.MetricIDs, utils.MetricFilterIDs, utils.MetricBlockers}
}

func (tps StatMdls) AsTPStats() (result []*utils.TPStatProfile) {
	filterMap := make(map[string]utils.StringSet)
	thresholdMap := make(map[string]utils.StringSet)
	statMetricsMap := make(map[string]map[string]*utils.MetricWithFilters)
	mst := make(map[string]*utils.TPStatProfile)
	for _, model := range tps {
		key := &utils.TenantID{Tenant: model.Tenant, ID: model.ID}
		st, found := mst[key.TenantID()]
		if !found {
			st = &utils.TPStatProfile{
				Tenant:      model.Tenant,
				TPid:        model.Tpid,
				ID:          model.ID,
				Stored:      model.Stored,
				Weights:     model.Weights,
				MinItems:    model.MinItems,
				TTL:         model.TTL,
				QueueLength: model.QueueLength,
			}
		}
		if model.Blockers != utils.EmptyString {
			st.Blockers = model.Blockers
		}
		if model.Stored {
			st.Stored = model.Stored
		}
		if model.Weights != utils.EmptyString {
			st.Weights = model.Weights
		}
		if model.MinItems != 0 {
			st.MinItems = model.MinItems
		}
		if model.TTL != utils.EmptyString {
			st.TTL = model.TTL
		}
		if model.QueueLength != 0 {
			st.QueueLength = model.QueueLength
		}
		if model.ThresholdIDs != utils.EmptyString {
			if _, has := thresholdMap[key.TenantID()]; !has {
				thresholdMap[key.TenantID()] = make(utils.StringSet)
			}
			thresholdMap[key.TenantID()].AddSlice(strings.Split(model.ThresholdIDs, utils.InfieldSep))
		}
		if model.FilterIDs != utils.EmptyString {
			if _, has := filterMap[key.TenantID()]; !has {
				filterMap[key.TenantID()] = make(utils.StringSet)
			}
			filterMap[key.TenantID()].AddSlice(strings.Split(model.FilterIDs, utils.InfieldSep))
		}
		if model.MetricIDs != utils.EmptyString {
			if _, has := statMetricsMap[key.TenantID()]; !has {
				statMetricsMap[key.TenantID()] = make(map[string]*utils.MetricWithFilters)
			}
			metricIDsSplit := strings.Split(model.MetricIDs, utils.InfieldSep)
			for _, metricID := range metricIDsSplit {
				stsMetric, found := statMetricsMap[key.TenantID()][metricID]
				if !found {
					stsMetric = &utils.MetricWithFilters{
						MetricID: metricID,
					}
				}
				if model.MetricFilterIDs != utils.EmptyString {
					filterIDs := strings.Split(model.MetricFilterIDs, utils.InfieldSep)
					stsMetric.FilterIDs = append(stsMetric.FilterIDs, filterIDs...)
				}
				if model.MetricBlockers != utils.EmptyString {
					stsMetric.Blockers = model.MetricBlockers
				}
				statMetricsMap[key.TenantID()][metricID] = stsMetric
			}
		}
		mst[key.TenantID()] = st
	}
	result = make([]*utils.TPStatProfile, len(mst))
	i := 0
	for tntID, st := range mst {
		result[i] = st
		result[i].FilterIDs = filterMap[tntID].AsSlice()
		result[i].ThresholdIDs = thresholdMap[tntID].AsSlice()
		for _, metric := range statMetricsMap[tntID] {
			result[i].Metrics = append(result[i].Metrics, metric)
		}
		i++
	}
	return
}

func APItoModelStats(st *utils.TPStatProfile) (mdls StatMdls) {
	if st != nil && len(st.Metrics) != 0 {
		for i, metric := range st.Metrics {
			mdl := &StatMdl{
				Tpid:   st.TPid,
				Tenant: st.Tenant,
				ID:     st.ID,
			}
			if i == 0 {
				for i, val := range st.FilterIDs {
					if i != 0 {
						mdl.FilterIDs += utils.InfieldSep
					}
					mdl.FilterIDs += val
				}
				mdl.QueueLength = st.QueueLength
				mdl.TTL = st.TTL
				mdl.MinItems = st.MinItems
				mdl.Stored = st.Stored
				mdl.Blockers = st.Blockers
				mdl.Weights = st.Weights
				for i, val := range st.ThresholdIDs {
					if i != 0 {
						mdl.ThresholdIDs += utils.InfieldSep
					}
					mdl.ThresholdIDs += val
				}
			}
			for i, val := range metric.FilterIDs {
				if i != 0 {
					mdl.MetricFilterIDs += utils.InfieldSep
				}
				mdl.MetricFilterIDs += val
			}
			mdl.MetricBlockers = metric.Blockers
			mdl.MetricIDs = metric.MetricID
			mdls = append(mdls, mdl)
		}
	}
	return
}

func APItoStats(tpST *utils.TPStatProfile, timezone string) (st *StatQueueProfile, err error) {
	st = &StatQueueProfile{
		Tenant:       tpST.Tenant,
		ID:           tpST.ID,
		FilterIDs:    make([]string, len(tpST.FilterIDs)),
		QueueLength:  tpST.QueueLength,
		MinItems:     tpST.MinItems,
		Metrics:      make([]*MetricWithFilters, len(tpST.Metrics)),
		Stored:       tpST.Stored,
		ThresholdIDs: make([]string, len(tpST.ThresholdIDs)),
	}
	if tpST.Weights != utils.EmptyString {
		if st.Weights, err = utils.NewDynamicWeightsFromString(tpST.Weights, utils.InfieldSep, utils.ANDSep); err != nil {
			return
		}
	}
	if tpST.Blockers != utils.EmptyString {
		if st.Blockers, err = utils.NewDynamicBlockersFromString(tpST.Blockers, utils.InfieldSep, utils.ANDSep); err != nil {
			return
		}
	}
	if tpST.TTL != utils.EmptyString {
		if st.TTL, err = utils.ParseDurationWithNanosecs(tpST.TTL); err != nil {
			return nil, err
		}
	}
	for i, metric := range tpST.Metrics {
		st.Metrics[i] = &MetricWithFilters{
			MetricID:  metric.MetricID,
			FilterIDs: metric.FilterIDs,
		}
		if metric.Blockers != utils.EmptyString {
			if st.Metrics[i].Blockers, err = utils.NewDynamicBlockersFromString(metric.Blockers, utils.InfieldSep, utils.ANDSep); err != nil {
				return
			}
		}
	}
	copy(st.ThresholdIDs, tpST.ThresholdIDs)
	copy(st.FilterIDs, tpST.FilterIDs)
	return st, nil
}

func StatQueueProfileToAPI(st *StatQueueProfile) (tpST *utils.TPStatProfile) {
	tpST = &utils.TPStatProfile{
		Tenant:       st.Tenant,
		ID:           st.ID,
		FilterIDs:    make([]string, len(st.FilterIDs)),
		QueueLength:  st.QueueLength,
		Metrics:      make([]*utils.MetricWithFilters, len(st.Metrics)),
		Blockers:     st.Blockers.String(utils.InfieldSep, utils.ANDSep),
		Stored:       st.Stored,
		Weights:      st.Weights.String(utils.InfieldSep, utils.ANDSep),
		MinItems:     st.MinItems,
		ThresholdIDs: make([]string, len(st.ThresholdIDs)),
	}
	for i, metric := range st.Metrics {
		tpST.Metrics[i] = &utils.MetricWithFilters{
			MetricID: metric.MetricID,
			Blockers: metric.Blockers.String(utils.InfieldSep, utils.ANDSep),
		}
		if len(metric.FilterIDs) != 0 {
			tpST.Metrics[i].FilterIDs = make([]string, len(metric.FilterIDs))
			copy(tpST.Metrics[i].FilterIDs, metric.FilterIDs)
		}
	}
	if st.TTL != time.Duration(0) {
		tpST.TTL = st.TTL.String()
	}
	copy(tpST.FilterIDs, st.FilterIDs)
	copy(tpST.ThresholdIDs, st.ThresholdIDs)
	return
}

type RankingMdls []*RankingMdl

func (tps RankingMdls) CSVHeader() (result []string) {
	return []string{"#" + utils.Tenant, utils.ID, utils.Schedule, utils.StatIDs,
		utils.MetricIDs, utils.Sorting, utils.SortingParameters, utils.Stored,
		utils.ThresholdIDs}
}

func (models RankingMdls) AsTPRanking() (result []*utils.TPRankingProfile) {
	thresholdMap := make(map[string]utils.StringSet)
	metricsMap := make(map[string]utils.StringSet)
	sortingParameterMap := make(map[string]utils.StringSet)
	sortingParameterSlice := make(map[string][]string)
	statsMap := make(map[string]utils.StringSet)
	mrg := make(map[string]*utils.TPRankingProfile)
	for _, model := range models {
		key := &utils.TenantID{Tenant: model.Tenant, ID: model.ID}
		rg, found := mrg[key.TenantID()]
		if !found {
			rg = &utils.TPRankingProfile{
				Tenant:   model.Tenant,
				TPid:     model.Tpid,
				ID:       model.ID,
				Schedule: model.Schedule,
				Sorting:  model.Sorting,
				Stored:   model.Stored,
			}
		}
		if model.Schedule != utils.EmptyString {
			rg.Schedule = model.Schedule
		}
		if model.Sorting != utils.EmptyString {
			rg.Sorting = model.Sorting
		}
		if model.Stored {
			rg.Stored = model.Stored
		}
		if model.StatIDs != utils.EmptyString {
			if _, has := statsMap[key.TenantID()]; !has {
				statsMap[key.TenantID()] = make(utils.StringSet)
			}
			statsMap[key.TenantID()].AddSlice(strings.Split(model.StatIDs, utils.InfieldSep))
		}
		if model.ThresholdIDs != utils.EmptyString {
			if _, has := thresholdMap[key.TenantID()]; !has {
				thresholdMap[key.TenantID()] = make(utils.StringSet)
			}
			thresholdMap[key.TenantID()].AddSlice(strings.Split(model.ThresholdIDs, utils.InfieldSep))
		}
		if model.SortingParameters != utils.EmptyString {
			if _, has := sortingParameterMap[key.TenantID()]; !has {
				sortingParameterMap[key.TenantID()] = make(utils.StringSet)
				sortingParameterSlice[key.TenantID()] = make([]string, 0)
			}
			spltSl := strings.Split(model.SortingParameters, utils.InfieldSep)
			for _, splt := range spltSl {
				if _, has := sortingParameterMap[key.TenantID()][splt]; !has {
					sortingParameterMap[key.TenantID()].Add(splt)
					sortingParameterSlice[key.TenantID()] = append(sortingParameterSlice[key.TenantID()], splt)
				}
			}
		}
		if model.MetricIDs != utils.EmptyString {
			if _, has := metricsMap[key.TenantID()]; !has {
				metricsMap[key.TenantID()] = make(utils.StringSet)
			}
			metricsMap[key.TenantID()].AddSlice(strings.Split(model.MetricIDs, utils.InfieldSep))
		}
		mrg[key.TenantID()] = rg
	}
	result = make([]*utils.TPRankingProfile, len(mrg))
	i := 0
	for tntID, rg := range mrg {
		result[i] = rg
		result[i].StatIDs = statsMap[tntID].AsSlice()
		result[i].MetricIDs = metricsMap[tntID].AsSlice()
		result[i].SortingParameters = sortingParameterSlice[tntID]
		result[i].ThresholdIDs = thresholdMap[tntID].AsOrderedSlice()
		i++
	}
	return
}

func APItoModelTPRanking(tpRG *utils.TPRankingProfile) (mdls RankingMdls) {
	if tpRG == nil {
		return
	}
	if len(tpRG.StatIDs) == 0 {
		mdl := &RankingMdl{
			Tpid:     tpRG.TPid,
			Tenant:   tpRG.Tenant,
			ID:       tpRG.ID,
			Schedule: tpRG.Schedule,
			Sorting:  tpRG.Sorting,
			Stored:   tpRG.Stored,
		}

		for i, val := range tpRG.ThresholdIDs {
			if i != 0 {
				mdl.ThresholdIDs += utils.InfieldSep
			}
			mdl.ThresholdIDs += val
		}
		for i, metric := range tpRG.MetricIDs {
			if i != 0 {
				mdl.MetricIDs += utils.InfieldSep
			}
			mdl.MetricIDs += metric
		}
		for i, sorting := range tpRG.SortingParameters {
			if i != 0 {
				mdl.SortingParameters += utils.InfieldSep
			}
			mdl.SortingParameters += sorting
		}

		mdls = append(mdls, mdl)
	}
	for i, stat := range tpRG.StatIDs {
		mdl := &RankingMdl{
			Tpid:   tpRG.TPid,
			Tenant: tpRG.Tenant,
			ID:     tpRG.ID,
		}
		if i == 0 {
			mdl.Schedule = tpRG.Schedule
			mdl.Sorting = tpRG.Sorting
			for i, val := range tpRG.ThresholdIDs {
				if i != 0 {
					mdl.ThresholdIDs += utils.InfieldSep
				}
				mdl.ThresholdIDs += val
			}
			for i, metric := range tpRG.MetricIDs {
				if i != 0 {
					mdl.MetricIDs += utils.InfieldSep
				}
				mdl.MetricIDs += metric
			}
			for i, sorting := range tpRG.SortingParameters {
				if i != 0 {
					mdl.SortingParameters += utils.InfieldSep
				}
				mdl.SortingParameters += sorting
			}
		}
		mdl.StatIDs = stat
		mdls = append(mdls, mdl)
	}
	return
}

func APItoRanking(tpRG *utils.TPRankingProfile) (rg *utils.RankingProfile, err error) {
	rg = &utils.RankingProfile{
		Tenant:            tpRG.Tenant,
		ID:                tpRG.ID,
		Schedule:          tpRG.Schedule,
		Sorting:           tpRG.Sorting,
		Stored:            tpRG.Stored,
		StatIDs:           make([]string, len(tpRG.StatIDs)),
		MetricIDs:         make([]string, len(tpRG.MetricIDs)),
		SortingParameters: make([]string, len(tpRG.SortingParameters)),
		ThresholdIDs:      make([]string, len(tpRG.ThresholdIDs)),
	}
	copy(rg.StatIDs, tpRG.StatIDs)
	copy(rg.ThresholdIDs, tpRG.ThresholdIDs)
	copy(rg.SortingParameters, tpRG.SortingParameters)
	copy(rg.MetricIDs, tpRG.MetricIDs)
	return rg, nil
}

func RankingProfileToAPI(rg *utils.RankingProfile) (tpRG *utils.TPRankingProfile) {
	tpRG = &utils.TPRankingProfile{
		Tenant:            rg.Tenant,
		ID:                rg.ID,
		Schedule:          rg.Schedule,
		Sorting:           rg.Sorting,
		Stored:            rg.Stored,
		StatIDs:           make([]string, len(rg.StatIDs)),
		MetricIDs:         make([]string, len(rg.MetricIDs)),
		SortingParameters: make([]string, len(rg.SortingParameters)),
		ThresholdIDs:      make([]string, len(rg.ThresholdIDs)),
	}
	copy(tpRG.StatIDs, rg.StatIDs)
	copy(tpRG.ThresholdIDs, rg.ThresholdIDs)
	copy(tpRG.MetricIDs, rg.MetricIDs)
	copy(tpRG.SortingParameters, rg.SortingParameters)
	return
}

type TrendMdls []*TrendMdl

func (tps TrendMdls) CSVHeader() (result []string) {
	return []string{"#" + utils.Tenant, utils.ID, utils.Schedule, utils.StatID,
		utils.Metrics, utils.TTL, utils.QueueLength, utils.MinItems, utils.CorrelationType, utils.Tolerance, utils.Stored, utils.ThresholdIDs}
}

func (models TrendMdls) AsTPTrends() (result []*utils.TPTrendsProfile) {
	thresholdsMap := make(map[string]utils.StringSet)
	trendMetricsMap := make(map[string]utils.StringSet)
	mtr := make(map[string]*utils.TPTrendsProfile)
	for _, model := range models {
		key := &utils.TenantID{Tenant: model.Tenant, ID: model.ID}
		tr, found := mtr[key.TenantID()]
		if !found {
			tr = &utils.TPTrendsProfile{
				Tenant:          model.Tenant,
				TPid:            model.Tpid,
				ID:              model.ID,
				Schedule:        model.Schedule,
				StatID:          model.StatID,
				TTL:             model.TTL,
				QueueLength:     model.QueueLength,
				MinItems:        model.MinItems,
				Tolerance:       model.Tolerance,
				Stored:          model.Stored,
				CorrelationType: model.CorrelationType,
			}
		}
		if model.Schedule != utils.EmptyString {
			tr.Schedule = model.Schedule
		}
		if model.StatID != utils.EmptyString {
			tr.StatID = model.StatID
		}
		if model.TTL != utils.EmptyString {
			tr.TTL = model.TTL
		}
		if model.QueueLength != 0 {
			tr.QueueLength = model.QueueLength
		}
		if model.MinItems != 0 {
			tr.MinItems = model.MinItems
		}
		if model.CorrelationType != utils.EmptyString {
			tr.CorrelationType = model.CorrelationType
		}
		if model.Tolerance != 0 {
			tr.Tolerance = model.Tolerance
		}
		if model.Stored {
			tr.Stored = true
		}
		if model.ThresholdIDs != utils.EmptyString {
			if _, has := thresholdsMap[key.TenantID()]; !has {
				thresholdsMap[key.TenantID()] = make(utils.StringSet)
			}
			thresholdsMap[key.TenantID()].AddSlice(strings.Split(model.ThresholdIDs, utils.InfieldSep))
		}
		if model.Metrics != utils.EmptyString {
			if _, has := trendMetricsMap[key.TenantID()]; !has {
				trendMetricsMap[key.TenantID()] = make(utils.StringSet)
			}
			trendMetricsMap[key.TenantID()].AddSlice(strings.Split(model.Metrics, utils.InfieldSep))
		}
		mtr[key.TenantID()] = tr
	}
	result = make([]*utils.TPTrendsProfile, len(mtr))
	i := 0
	for tntId, sr := range mtr {
		result[i] = sr
		result[i].ThresholdIDs = thresholdsMap[tntId].AsSlice()
		result[i].Metrics = trendMetricsMap[tntId].AsSlice()
		i++
	}
	return
}

func APItoModelTrends(tr *utils.TPTrendsProfile) (mdls TrendMdls) {
	if tr != nil {
		mdl := &TrendMdl{
			Tpid:   tr.TPid,
			Tenant: tr.Tenant,
			ID:     tr.ID,
		}
		mdl.Schedule = tr.Schedule
		mdl.QueueLength = tr.QueueLength
		mdl.StatID = tr.StatID
		mdl.TTL = tr.TTL
		mdl.MinItems = tr.MinItems
		mdl.CorrelationType = tr.CorrelationType
		mdl.Tolerance = tr.Tolerance
		mdl.Stored = tr.Stored
		for i, val := range tr.ThresholdIDs {
			if i != 0 {
				mdl.ThresholdIDs += utils.InfieldSep
			}
			mdl.ThresholdIDs += val
		}
		for i, val := range tr.Metrics {
			if i != 0 {
				mdl.Metrics += utils.InfieldSep
			}
			mdl.Metrics += val
		}
		mdls = append(mdls, mdl)
	}
	return
}

func APItoTrends(tpTR *utils.TPTrendsProfile) (tr *utils.TrendProfile, err error) {
	tr = &utils.TrendProfile{
		Tenant:          tpTR.Tenant,
		ID:              tpTR.ID,
		StatID:          tpTR.StatID,
		Schedule:        tpTR.Schedule,
		QueueLength:     tpTR.QueueLength,
		ThresholdIDs:    make([]string, len(tpTR.ThresholdIDs)),
		Metrics:         make([]string, len(tpTR.Metrics)),
		MinItems:        tpTR.MinItems,
		CorrelationType: tpTR.CorrelationType,
		Tolerance:       tpTR.Tolerance,
	}
	if tpTR.TTL != utils.EmptyString {
		if tr.TTL, err = utils.ParseDurationWithNanosecs(tpTR.TTL); err != nil {
			return
		}
	}
	copy(tr.ThresholdIDs, tpTR.ThresholdIDs)
	copy(tr.Metrics, tpTR.Metrics)

	return
}

func TrendProfileToAPI(tr *utils.TrendProfile) (tpTR *utils.TPTrendsProfile) {
	tpTR = &utils.TPTrendsProfile{
		Tenant:          tr.Tenant,
		ID:              tr.ID,
		Schedule:        tr.Schedule,
		StatID:          tr.StatID,
		ThresholdIDs:    make([]string, len(tr.ThresholdIDs)),
		Metrics:         make([]string, len(tr.Metrics)),
		QueueLength:     tr.QueueLength,
		MinItems:        tr.MinItems,
		CorrelationType: tr.CorrelationType,
		Tolerance:       tr.Tolerance,
		Stored:          tr.Stored,
	}
	if tr.TTL != time.Duration(0) {
		tpTR.TTL = tr.TTL.String()
	}
	copy(tpTR.ThresholdIDs, tr.ThresholdIDs)
	copy(tpTR.Metrics, tr.Metrics)
	return
}

type ThresholdMdls []*ThresholdMdl

// CSVHeader return the header for csv fields as a slice of string
func (tps ThresholdMdls) CSVHeader() (result []string) {
	return []string{"#" + utils.Tenant, utils.ID, utils.FilterIDs, utils.Weights,
		utils.MaxHits, utils.MinHits, utils.MinSleep,
		utils.Blocker, utils.ActionProfileIDs, utils.Async, utils.EeIDs}
}

func (tps ThresholdMdls) AsTPThreshold() (result []*utils.TPThresholdProfile) {
	mst := make(map[string]*utils.TPThresholdProfile)
	filterMap := make(map[string]utils.StringSet)
	actionMap := make(map[string]utils.StringSet)
	eesIDsMap := make(map[string]utils.StringSet)
	for _, tp := range tps {
		tenID := (&utils.TenantID{Tenant: tp.Tenant, ID: tp.ID}).TenantID()
		th, found := mst[tenID]
		if !found {
			th = &utils.TPThresholdProfile{
				TPid:     tp.Tpid,
				Tenant:   tp.Tenant,
				ID:       tp.ID,
				Blocker:  tp.Blocker,
				MaxHits:  tp.MaxHits,
				MinHits:  tp.MinHits,
				MinSleep: tp.MinSleep,
				Async:    tp.Async,
			}
		}
		if tp.ActionProfileIDs != utils.EmptyString {
			if _, has := actionMap[tenID]; !has {
				actionMap[tenID] = make(utils.StringSet)
			}
			actionMap[tenID].AddSlice(strings.Split(tp.ActionProfileIDs, utils.InfieldSep))
		}
		if tp.Weights != "" {
			th.Weights = tp.Weights
		}
		if tp.FilterIDs != utils.EmptyString {
			if _, has := filterMap[tenID]; !has {
				filterMap[tenID] = make(utils.StringSet)
			}
			filterMap[tenID].AddSlice(strings.Split(tp.FilterIDs, utils.InfieldSep))
		}
		if tp.EeIDs != utils.EmptyString {
			if _, has := eesIDsMap[tenID]; !has {
				eesIDsMap[tenID] = make(utils.StringSet)
			}
			eesIDsMap[tenID].AddSlice(strings.Split(tp.EeIDs, utils.InfieldSep))
		}

		mst[tenID] = th
	}
	result = make([]*utils.TPThresholdProfile, len(mst))
	i := 0
	for tntID, th := range mst {
		result[i] = th
		result[i].FilterIDs = filterMap[tntID].AsSlice()
		result[i].ActionProfileIDs = actionMap[tntID].AsSlice()
		result[i].EeIDs = eesIDsMap[tntID].AsSlice()
		i++
	}
	return
}

func APItoModelTPThreshold(th *utils.TPThresholdProfile) (mdls ThresholdMdls) {
	if th != nil {
		if len(th.ActionProfileIDs) == 0 {
			return
		}
		min := len(th.FilterIDs)
		if min > len(th.ActionProfileIDs) {
			min = len(th.ActionProfileIDs)
		}
		for i := 0; i < min; i++ {
			mdl := &ThresholdMdl{
				Tpid:   th.TPid,
				Tenant: th.Tenant,
				ID:     th.ID,
			}
			if i == 0 {
				mdl.Blocker = th.Blocker
				mdl.Weights = th.Weights
				mdl.MaxHits = th.MaxHits
				mdl.MinHits = th.MinHits
				mdl.MinSleep = th.MinSleep
				mdl.Async = th.Async

				for i, val := range th.EeIDs {
					if i != 0 {
						mdl.EeIDs += utils.InfieldSep
					}
					mdl.EeIDs += val
				}
			}
			mdl.FilterIDs = th.FilterIDs[i]
			mdl.ActionProfileIDs = th.ActionProfileIDs[i]
			mdls = append(mdls, mdl)
		}

		if len(th.FilterIDs)-min > 0 {
			for i := min; i < len(th.FilterIDs); i++ {
				mdl := &ThresholdMdl{
					Tpid:   th.TPid,
					Tenant: th.Tenant,
					ID:     th.ID,
				}
				mdl.FilterIDs = th.FilterIDs[i]
				mdls = append(mdls, mdl)
			}
		}
		if len(th.ActionProfileIDs)-min > 0 {
			for i := min; i < len(th.ActionProfileIDs); i++ {
				mdl := &ThresholdMdl{
					Tpid:   th.TPid,
					Tenant: th.Tenant,
					ID:     th.ID,
				}
				if min == 0 && i == 0 {
					mdl.Blocker = th.Blocker
					mdl.Weights = th.Weights
					mdl.MaxHits = th.MaxHits
					mdl.MinHits = th.MinHits
					mdl.MinSleep = th.MinSleep
					mdl.Async = th.Async
				}
				mdl.ActionProfileIDs = th.ActionProfileIDs[i]
				mdls = append(mdls, mdl)
			}
		}
	}
	return
}

func APItoThresholdProfile(tpTH *utils.TPThresholdProfile, timezone string) (th *ThresholdProfile, err error) {
	th = &ThresholdProfile{
		Tenant:           tpTH.Tenant,
		ID:               tpTH.ID,
		MaxHits:          tpTH.MaxHits,
		MinHits:          tpTH.MinHits,
		Blocker:          tpTH.Blocker,
		Async:            tpTH.Async,
		ActionProfileIDs: make([]string, len(tpTH.ActionProfileIDs)),
		FilterIDs:        make([]string, len(tpTH.FilterIDs)),
		EeIDs:            make([]string, len(tpTH.EeIDs)),
	}
	if tpTH.Weights != utils.EmptyString {
		if th.Weights, err = utils.NewDynamicWeightsFromString(tpTH.Weights, utils.InfieldSep, utils.ANDSep); err != nil {
			return
		}
	}
	if tpTH.MinSleep != utils.EmptyString {
		if th.MinSleep, err = utils.ParseDurationWithNanosecs(tpTH.MinSleep); err != nil {
			return nil, err
		}
	}
	copy(th.EeIDs, tpTH.EeIDs)
	copy(th.ActionProfileIDs, tpTH.ActionProfileIDs)
	copy(th.FilterIDs, tpTH.FilterIDs)
	return th, nil
}

func ThresholdProfileToAPI(th *ThresholdProfile) (tpTH *utils.TPThresholdProfile) {
	tpTH = &utils.TPThresholdProfile{
		Tenant:           th.Tenant,
		ID:               th.ID,
		FilterIDs:        make([]string, len(th.FilterIDs)),
		MaxHits:          th.MaxHits,
		MinHits:          th.MinHits,
		Blocker:          th.Blocker,
		Weights:          th.Weights.String(utils.InfieldSep, utils.ANDSep),
		ActionProfileIDs: make([]string, len(th.ActionProfileIDs)),
		Async:            th.Async,
		EeIDs:            make([]string, len(th.EeIDs)),
	}
	if th.MinSleep != time.Duration(0) {
		tpTH.MinSleep = th.MinSleep.String()
	}
	copy(tpTH.FilterIDs, th.FilterIDs)
	copy(tpTH.ActionProfileIDs, th.ActionProfileIDs)
	copy(tpTH.EeIDs, th.EeIDs)
	return
}

type FilterMdls []*FilterMdl

// CSVHeader return the header for csv fields as a slice of string
func (tps FilterMdls) CSVHeader() (result []string) {
	return []string{"#" + utils.Tenant, utils.ID, utils.Type, utils.Element,
		utils.Values}
}

func (tps FilterMdls) AsTPFilter() (result []*utils.TPFilterProfile) {
	mst := make(map[string]*utils.TPFilterProfile)
	filterRules := make(map[string]*utils.TPFilter)
	for _, tp := range tps {
		tenID := (&utils.TenantID{Tenant: tp.Tenant, ID: tp.ID}).TenantID()
		th, found := mst[tenID]
		if !found {
			th = &utils.TPFilterProfile{
				TPid:   tp.Tpid,
				Tenant: tp.Tenant,
				ID:     tp.ID,
			}
		}
		if tp.Type != utils.EmptyString {
			var vals []string
			if tp.Values != utils.EmptyString {
				vals = splitDynFltrValues(tp.Values, utils.InfieldSep)
			}
			key := utils.ConcatenatedKey(tenID, tp.Type, tp.Element)
			if f, has := filterRules[key]; has {
				f.Values = append(f.Values, vals...)
			} else {
				f = &utils.TPFilter{
					Type:    tp.Type,
					Element: tp.Element,
					Values:  vals,
				}
				th.Filters = append(th.Filters, f)
				filterRules[key] = f
			}
		}
		mst[tenID] = th
	}
	result = make([]*utils.TPFilterProfile, len(mst))
	i := 0
	for _, th := range mst {
		result[i] = th
		i++
	}
	return
}

func APItoModelTPFilter(th *utils.TPFilterProfile) (mdls FilterMdls) {
	if th == nil || len(th.Filters) == 0 {
		return
	}
	for _, fltr := range th.Filters {
		mdl := &FilterMdl{
			Tpid:   th.TPid,
			Tenant: th.Tenant,
			ID:     th.ID,
		}
		mdl.Type = fltr.Type
		mdl.Element = fltr.Element
		for i, val := range fltr.Values {
			if i != 0 {
				mdl.Values += utils.InfieldSep
			}
			mdl.Values += val
		}
		mdls = append(mdls, mdl)
	}
	return
}

func APItoFilter(tpTH *utils.TPFilterProfile, timezone string) (th *Filter, err error) {
	th = &Filter{
		Tenant: tpTH.Tenant,
		ID:     tpTH.ID,
		Rules:  make([]*FilterRule, len(tpTH.Filters)),
	}
	for i, f := range tpTH.Filters {
		rf := &FilterRule{Type: f.Type, Element: f.Element, Values: f.Values}
		if err := rf.CompileValues(); err != nil {
			return nil, err
		}
		th.Rules[i] = rf
	}
	return th, nil
}

func FilterToTPFilter(f *Filter) (tpFltr *utils.TPFilterProfile) {
	tpFltr = &utils.TPFilterProfile{
		Tenant:  f.Tenant,
		ID:      f.ID,
		Filters: make([]*utils.TPFilter, len(f.Rules)),
	}
	for i, reqFltr := range f.Rules {
		tpFltr.Filters[i] = &utils.TPFilter{
			Type:    reqFltr.Type,
			Element: reqFltr.Element,
			Values:  make([]string, len(reqFltr.Values)),
		}
		copy(tpFltr.Filters[i].Values, reqFltr.Values)
	}
	return
}

type RouteMdls []*RouteMdl

// CSVHeader return the header for csv fields as a slice of string
func (tps RouteMdls) CSVHeader() (result []string) {
	return []string{"#" + utils.Tenant, utils.ID, utils.FilterIDs, utils.Weights,
		utils.Sorting, utils.SortingParameters, utils.RouteID, utils.RouteFilterIDs,
		utils.RouteAccountIDs, utils.RouteRateProfileIDs, utils.RouteRateProfileIDs,
		utils.RouteResourceIDs, utils.RouteStatIDs, utils.RouteWeights, utils.RouteBlockers,
		utils.RouteParameters,
	}
}

func (tps RouteMdls) AsTPRouteProfile() (result []*utils.TPRouteProfile) {
	filterMap := make(map[string]utils.StringSet)
	tpRouteProfileMap := make(map[string]*utils.TPRouteProfile)
	routeMap := make(map[string]map[string]*utils.TPRoute)
	sortingParameterMap := make(map[string]utils.StringSet)
	for _, tp := range tps {
		tenID := (&utils.TenantID{Tenant: tp.Tenant, ID: tp.ID}).TenantID()
		tpRouteProfile, found := tpRouteProfileMap[tenID]
		if !found {
			tpRouteProfile = &utils.TPRouteProfile{
				TPid:              tp.Tpid,
				Tenant:            tp.Tenant,
				ID:                tp.ID,
				SortingParameters: []string{},
			}
		}
		if tp.RouteID != utils.EmptyString {
			if _, has := routeMap[tenID]; !has {
				routeMap[tenID] = make(map[string]*utils.TPRoute)
			}
			routeID := tp.RouteID
			if tp.RouteFilterIDs != utils.EmptyString {
				routeID = utils.ConcatenatedKey(routeID,
					utils.NewStringSet(strings.Split(tp.RouteFilterIDs, utils.InfieldSep)).Sha1())
			}
			tpRoute, found := routeMap[tenID][routeID]
			if !found {
				tpRoute = &utils.TPRoute{
					ID:              tp.RouteID,
					Weights:         tp.RouteWeights,
					Blockers:        tp.RouteBlockers,
					RouteParameters: tp.RouteParameters,
				}
			}
			if tp.RouteFilterIDs != utils.EmptyString {
				routeFilterSplit := strings.Split(tp.RouteFilterIDs, utils.InfieldSep)
				tpRoute.FilterIDs = append(tpRoute.FilterIDs, routeFilterSplit...)
			}
			if tp.RouteRateProfileIDs != utils.EmptyString {
				ratingPlanSplit := strings.Split(tp.RouteRateProfileIDs, utils.InfieldSep)
				tpRoute.RateProfileIDs = append(tpRoute.RateProfileIDs, ratingPlanSplit...)
			}
			if tp.RouteResourceIDs != utils.EmptyString {
				resSplit := strings.Split(tp.RouteResourceIDs, utils.InfieldSep)
				tpRoute.ResourceIDs = append(tpRoute.ResourceIDs, resSplit...)
			}
			if tp.RouteStatIDs != utils.EmptyString {
				statSplit := strings.Split(tp.RouteStatIDs, utils.InfieldSep)
				tpRoute.StatIDs = append(tpRoute.StatIDs, statSplit...)
			}
			if tp.RouteAccountIDs != utils.EmptyString {
				accSplit := strings.Split(tp.RouteAccountIDs, utils.InfieldSep)
				tpRoute.AccountIDs = append(tpRoute.AccountIDs, accSplit...)
			}
			routeMap[tenID][routeID] = tpRoute
		}
		if tp.Sorting != utils.EmptyString {
			tpRouteProfile.Sorting = tp.Sorting
		}
		if tp.SortingParameters != utils.EmptyString {
			if _, has := sortingParameterMap[tenID]; !has {
				sortingParameterMap[tenID] = make(utils.StringSet)
			}
			sortingParameterMap[tenID].AddSlice(strings.Split(tp.SortingParameters, utils.InfieldSep))
		}
		if tp.Weights != utils.EmptyString {
			tpRouteProfile.Weights = tp.Weights
		}
		if tp.Blockers != utils.EmptyString {
			tpRouteProfile.Blockers = tp.Blockers
		}
		if tp.FilterIDs != utils.EmptyString {
			if _, has := filterMap[tenID]; !has {
				filterMap[tenID] = make(utils.StringSet)
			}
			filterMap[tenID].AddSlice(strings.Split(tp.FilterIDs, utils.InfieldSep))
		}
		tpRouteProfileMap[tenID] = tpRouteProfile
	}
	result = make([]*utils.TPRouteProfile, len(tpRouteProfileMap))
	i := 0
	for tntID, tpRouteProfile := range tpRouteProfileMap {
		result[i] = tpRouteProfile
		for _, routeData := range routeMap[tntID] {
			result[i].Routes = append(result[i].Routes, routeData)
		}
		result[i].FilterIDs = filterMap[tntID].AsSlice()
		result[i].SortingParameters = sortingParameterMap[tntID].AsSlice()
		i++
	}
	return
}

func APItoModelTPRoutes(st *utils.TPRouteProfile) (mdls RouteMdls) {
	if len(st.Routes) == 0 {
		return
	}
	for i, route := range st.Routes {
		mdl := &RouteMdl{
			Tenant: st.Tenant,
			Tpid:   st.TPid,
			ID:     st.ID,
		}
		if i == 0 {
			mdl.Sorting = st.Sorting
			mdl.Weights = st.Weights
			mdl.Blockers = st.Blockers
			for i, val := range st.FilterIDs {
				if i != 0 {
					mdl.FilterIDs += utils.InfieldSep
				}
				mdl.FilterIDs += val
			}
			for i, val := range st.SortingParameters {
				if i != 0 {
					mdl.SortingParameters += utils.InfieldSep
				}
				mdl.SortingParameters += val
			}
		}
		mdl.RouteID = route.ID
		for i, val := range route.AccountIDs {
			if i != 0 {
				mdl.RouteAccountIDs += utils.InfieldSep
			}
			mdl.RouteAccountIDs += val
		}
		for i, val := range route.RateProfileIDs {
			if i != 0 {
				mdl.RouteRateProfileIDs += utils.InfieldSep
			}
			mdl.RouteRateProfileIDs += val
		}
		for i, val := range route.FilterIDs {
			if i != 0 {
				mdl.RouteFilterIDs += utils.InfieldSep
			}
			mdl.RouteFilterIDs += val
		}
		for i, val := range route.ResourceIDs {
			if i != 0 {
				mdl.RouteResourceIDs += utils.InfieldSep
			}
			mdl.RouteResourceIDs += val
		}
		for i, val := range route.StatIDs {
			if i != 0 {
				mdl.RouteStatIDs += utils.InfieldSep
			}
			mdl.RouteStatIDs += val
		}
		mdl.RouteWeights = route.Weights
		mdl.RouteParameters = route.RouteParameters
		mdl.RouteBlockers = route.Blockers
		mdls = append(mdls, mdl)
	}
	return
}

func APItoRouteProfile(tpRp *utils.TPRouteProfile, timezone string) (rp *utils.RouteProfile, err error) {
	rp = &utils.RouteProfile{
		Tenant:            tpRp.Tenant,
		ID:                tpRp.ID,
		Sorting:           tpRp.Sorting,
		Routes:            make([]*utils.Route, len(tpRp.Routes)),
		SortingParameters: make([]string, len(tpRp.SortingParameters)),
		FilterIDs:         make([]string, len(tpRp.FilterIDs)),
	}
	if tpRp.Weights != utils.EmptyString {
		rp.Weights, err = utils.NewDynamicWeightsFromString(tpRp.Weights, utils.InfieldSep, utils.ANDSep)
		if err != nil {
			return nil, err
		}
	}
	if tpRp.Blockers != utils.EmptyString {
		rp.Blockers, err = utils.NewDynamicBlockersFromString(tpRp.Blockers, utils.InfieldSep, utils.ANDSep)
		if err != nil {
			return nil, err
		}
	}
	copy(rp.SortingParameters, tpRp.SortingParameters)
	copy(rp.FilterIDs, tpRp.FilterIDs)
	for i, route := range tpRp.Routes {
		rp.Routes[i] = &utils.Route{
			ID:              route.ID,
			RateProfileIDs:  route.RateProfileIDs,
			AccountIDs:      route.AccountIDs,
			FilterIDs:       route.FilterIDs,
			ResourceIDs:     route.ResourceIDs,
			StatIDs:         route.StatIDs,
			RouteParameters: route.RouteParameters,
		}
		if route.Weights != utils.EmptyString {
			rp.Routes[i].Weights, err = utils.NewDynamicWeightsFromString(route.Weights, utils.InfieldSep, utils.ANDSep)
			if err != nil {
				return nil, err
			}
		}
		if route.Blockers != utils.EmptyString {
			rp.Routes[i].Blockers, err = utils.NewDynamicBlockersFromString(route.Blockers, utils.InfieldSep, utils.ANDSep)
			if err != nil {
				return nil, err
			}
		}
	}
	return rp, nil
}

func RouteProfileToAPI(rp *utils.RouteProfile) (tpRp *utils.TPRouteProfile) {
	tpRp = &utils.TPRouteProfile{
		Tenant:            rp.Tenant,
		ID:                rp.ID,
		FilterIDs:         make([]string, len(rp.FilterIDs)),
		Weights:           rp.Weights.String(utils.InfieldSep, utils.ANDSep),
		Blockers:          rp.Blockers.String(utils.InfieldSep, utils.ANDSep),
		Sorting:           rp.Sorting,
		SortingParameters: make([]string, len(rp.SortingParameters)),
		Routes:            make([]*utils.TPRoute, len(rp.Routes)),
	}

	for i, route := range rp.Routes {
		tpRp.Routes[i] = &utils.TPRoute{
			ID:              route.ID,
			FilterIDs:       route.FilterIDs,
			AccountIDs:      route.AccountIDs,
			RateProfileIDs:  route.RateProfileIDs,
			ResourceIDs:     route.ResourceIDs,
			StatIDs:         route.StatIDs,
			Weights:         route.Weights.String(utils.InfieldSep, utils.ANDSep),
			Blockers:        route.Blockers.String(utils.InfieldSep, utils.ANDSep),
			RouteParameters: route.RouteParameters,
		}
	}
	copy(tpRp.FilterIDs, rp.FilterIDs)
	copy(tpRp.SortingParameters, rp.SortingParameters)
	return
}

type AttributeMdls []*AttributeMdl

// CSVHeader return the header for csv fields as a slice of string
func (tps AttributeMdls) CSVHeader() (result []string) {
	return []string{"#" + utils.Tenant, utils.ID, utils.FilterIDs, utils.Weights, utils.Blockers, utils.AttributeFilterIDs, utils.AttributeBlockers, utils.Path, utils.Type, utils.Value}
}

func (tps AttributeMdls) AsTPAttributes() (result []*utils.TPAttributeProfile) {
	mst := make(map[string]*utils.TPAttributeProfile)
	filterMap := make(map[string]utils.StringSet)
	for _, tp := range tps {
		key := &utils.TenantID{Tenant: tp.Tenant, ID: tp.ID}
		tenID := (&utils.TenantID{Tenant: tp.Tenant, ID: tp.ID}).TenantID()
		th, found := mst[tenID]
		if !found {
			th = &utils.TPAttributeProfile{
				TPid:   tp.Tpid,
				Tenant: tp.Tenant,
				ID:     tp.ID,
			}
		}
		if tp.Blockers != utils.EmptyString {
			th.Blockers = tp.Blockers
		}
		if tp.Weights != utils.EmptyString {
			th.Weights = tp.Weights
		}
		if tp.FilterIDs != utils.EmptyString {
			if _, has := filterMap[key.TenantID()]; !has {
				filterMap[key.TenantID()] = make(utils.StringSet)
			}
			filterMap[key.TenantID()].AddSlice(strings.Split(tp.FilterIDs, utils.InfieldSep))
		}
		if tp.Path != utils.EmptyString {
			filterIDs := make([]string, 0)
			if tp.AttributeFilterIDs != utils.EmptyString {
				filterIDs = append(filterIDs, strings.Split(tp.AttributeFilterIDs, utils.InfieldSep)...)
			}
			th.Attributes = append(th.Attributes, &utils.TPAttribute{
				FilterIDs: filterIDs,
				Blockers:  tp.AttributeBlockers,
				Type:      tp.Type,
				Path:      tp.Path,
				Value:     tp.Value,
			})
		}
		mst[key.TenantID()] = th
	}
	result = make([]*utils.TPAttributeProfile, len(mst))
	i := 0
	for tntID, th := range mst {
		result[i] = th
		result[i].FilterIDs = filterMap[tntID].AsSlice()
		i++
	}
	return
}

func APItoModelTPAttribute(ap *utils.TPAttributeProfile) (mdls AttributeMdls) {
	if len(ap.Attributes) == 0 {
		return
	}
	for i, reqAttribute := range ap.Attributes {
		mdl := &AttributeMdl{
			Tpid:   ap.TPid,
			Tenant: ap.Tenant,
			ID:     ap.ID,
		}
		if i == 0 {
			for i, val := range ap.FilterIDs {
				if i != 0 {
					mdl.FilterIDs += utils.InfieldSep
				}
				mdl.FilterIDs += val
			}
			if ap.Blockers != utils.EmptyString {
				mdl.Blockers = ap.Blockers
			}
			if ap.Weights != utils.EmptyString {
				mdl.Weights = ap.Weights
			}
		}
		mdl.AttributeBlockers = reqAttribute.Blockers
		mdl.Path = reqAttribute.Path
		mdl.Value = reqAttribute.Value
		mdl.Type = reqAttribute.Type
		mdl.AttributeFilterIDs = strings.Join(reqAttribute.FilterIDs, utils.InfieldSep)
		mdls = append(mdls, mdl)
	}
	return
}

func APItoAttributeProfile(tpAttr *utils.TPAttributeProfile, timezone string) (attrPrf *utils.AttributeProfile, err error) {
	attrPrf = &utils.AttributeProfile{
		Tenant:     tpAttr.Tenant,
		ID:         tpAttr.ID,
		FilterIDs:  make([]string, len(tpAttr.FilterIDs)),
		Attributes: make([]*utils.Attribute, len(tpAttr.Attributes)),
	}
	if tpAttr.Blockers != utils.EmptyString {
		if attrPrf.Blockers, err = utils.NewDynamicBlockersFromString(tpAttr.Blockers, utils.InfieldSep, utils.ANDSep); err != nil {
			return
		}
	}
	if tpAttr.Weights != utils.EmptyString {
		if attrPrf.Weights, err = utils.NewDynamicWeightsFromString(tpAttr.Weights, utils.InfieldSep, utils.ANDSep); err != nil {
			return
		}
	}
	copy(attrPrf.FilterIDs, tpAttr.FilterIDs)
	for i, reqAttr := range tpAttr.Attributes {
		if reqAttr.Path == utils.EmptyString { // we do not suppot empty Path in Attributes
			err = fmt.Errorf("empty path in AttributeProfile <%s>", attrPrf.TenantID())
			return
		}
		sbstPrsr, err := utils.NewRSRParsers(reqAttr.Value, utils.RSRSep)
		if err != nil {
			return nil, err
		}
		attrPrf.Attributes[i] = &utils.Attribute{
			FilterIDs: reqAttr.FilterIDs,
			Path:      reqAttr.Path,
			Type:      reqAttr.Type,
			Value:     sbstPrsr,
		}
		if reqAttr.Blockers != utils.EmptyString {
			if attrPrf.Attributes[i].Blockers, err = utils.NewDynamicBlockersFromString(reqAttr.Blockers, utils.InfieldSep, utils.ANDSep); err != nil {
				return nil, err
			}
		}
	}
	return attrPrf, nil
}

func AttributeProfileToAPI(attrPrf *utils.AttributeProfile) (tpAttr *utils.TPAttributeProfile) {
	tpAttr = &utils.TPAttributeProfile{
		Tenant:     attrPrf.Tenant,
		ID:         attrPrf.ID,
		FilterIDs:  make([]string, len(attrPrf.FilterIDs)),
		Attributes: make([]*utils.TPAttribute, len(attrPrf.Attributes)),
		Blockers:   attrPrf.Blockers.String(utils.InfieldSep, utils.ANDSep),
		Weights:    attrPrf.Weights.String(utils.InfieldSep, utils.ANDSep),
	}
	copy(tpAttr.FilterIDs, attrPrf.FilterIDs)
	for i, attr := range attrPrf.Attributes {
		tpAttr.Attributes[i] = &utils.TPAttribute{
			FilterIDs: attr.FilterIDs,
			Blockers:  attr.Blockers.String(utils.InfieldSep, utils.ANDSep),
			Path:      attr.Path,
			Type:      attr.Type,
			Value:     attr.Value.GetRule(),
		}
	}
	return
}

type ChargerMdls []*ChargerMdl

// CSVHeader return the header for csv fields as a slice of string
func (tps ChargerMdls) CSVHeader() (result []string) {
	return []string{"#" + utils.Tenant, utils.ID, utils.FilterIDs, utils.Weights,
		utils.Blockers, utils.RunID, utils.AttributeIDs}
}

func (tps ChargerMdls) AsTPChargers() (result []*utils.TPChargerProfile) {
	mst := make(map[string]*utils.TPChargerProfile)
	filterMap := make(map[string]utils.StringSet)
	attributeMap := make(map[string][]string)
	for _, tp := range tps {
		tntID := (&utils.TenantID{Tenant: tp.Tenant, ID: tp.ID}).TenantID()
		tpCPP, found := mst[tntID]
		if !found {
			tpCPP = &utils.TPChargerProfile{
				TPid:   tp.Tpid,
				Tenant: tp.Tenant,
				ID:     tp.ID,
			}
		}
		if tp.Weights != utils.EmptyString {
			tpCPP.Weights = tp.Weights
		}
		if tp.Blockers != utils.EmptyString {
			tpCPP.Blockers = tp.Blockers
		}
		if tp.FilterIDs != utils.EmptyString {
			if _, has := filterMap[tntID]; !has {
				filterMap[tntID] = make(utils.StringSet)
			}
			filterMap[tntID].AddSlice(strings.Split(tp.FilterIDs, utils.InfieldSep))
		}
		if tp.RunID != utils.EmptyString {
			tpCPP.RunID = tp.RunID
		}
		if tp.AttributeIDs != utils.EmptyString {
			attributeSplit := strings.Split(tp.AttributeIDs, utils.InfieldSep)
			var inlineAttribute string
			var dynam bool
			for _, attribute := range attributeSplit {
				if !dynam && !strings.HasPrefix(attribute, utils.Meta) {
					if inlineAttribute != utils.EmptyString {
						attributeMap[tntID] = append(attributeMap[tntID], inlineAttribute[1:])
						inlineAttribute = utils.EmptyString
					}
					attributeMap[tntID] = append(attributeMap[tntID], attribute)
					continue
				}
				if dynam {
					dynam = !strings.Contains(attribute, string(utils.RSRDynEndChar))
				} else {
					dynam = strings.Contains(attribute, string(utils.RSRDynStartChar))
				}
				inlineAttribute += utils.InfieldSep + attribute
			}
			if inlineAttribute != utils.EmptyString {
				attributeMap[tntID] = append(attributeMap[tntID], inlineAttribute[1:])
				inlineAttribute = utils.EmptyString
			}
		}
		mst[tntID] = tpCPP
	}
	result = make([]*utils.TPChargerProfile, len(mst))
	i := 0
	for tntID, tp := range mst {
		result[i] = tp
		result[i].FilterIDs = filterMap[tntID].AsSlice()
		result[i].AttributeIDs = make([]string, 0, len(attributeMap[tntID]))
		result[i].AttributeIDs = append(result[i].AttributeIDs, attributeMap[tntID]...)
		i++
	}
	return
}

func APItoModelTPCharger(tpCPP *utils.TPChargerProfile) (mdls ChargerMdls) {
	if tpCPP != nil {
		min := len(tpCPP.FilterIDs)
		isFilter := true
		if min > len(tpCPP.AttributeIDs) {
			min = len(tpCPP.AttributeIDs)
			isFilter = false
		}
		if min == 0 {
			mdl := &ChargerMdl{
				Tenant:   tpCPP.Tenant,
				Tpid:     tpCPP.TPid,
				ID:       tpCPP.ID,
				Weights:  tpCPP.Weights,
				Blockers: tpCPP.Blockers,
				RunID:    tpCPP.RunID,
			}
			if isFilter && len(tpCPP.AttributeIDs) > 0 {
				mdl.AttributeIDs = tpCPP.AttributeIDs[0]
			} else if len(tpCPP.FilterIDs) > 0 {
				mdl.FilterIDs = tpCPP.FilterIDs[0]
			}
			min = 1
			mdls = append(mdls, mdl)
		} else {
			for i := 0; i < min; i++ {
				mdl := &ChargerMdl{
					Tenant: tpCPP.Tenant,
					Tpid:   tpCPP.TPid,
					ID:     tpCPP.ID,
				}
				if i == 0 {
					mdl.Weights = tpCPP.Weights
					mdl.Blockers = tpCPP.Blockers
					mdl.RunID = tpCPP.RunID
				}
				mdl.AttributeIDs = tpCPP.AttributeIDs[i]
				mdl.FilterIDs = tpCPP.FilterIDs[i]
				mdls = append(mdls, mdl)
			}
		}
		if len(tpCPP.FilterIDs)-min > 0 {
			for i := min; i < len(tpCPP.FilterIDs); i++ {
				mdl := &ChargerMdl{
					Tenant: tpCPP.Tenant,
					Tpid:   tpCPP.TPid,
					ID:     tpCPP.ID,
				}
				mdl.FilterIDs = tpCPP.FilterIDs[i]
				mdls = append(mdls, mdl)
			}
		}
		if len(tpCPP.AttributeIDs)-min > 0 {
			for i := min; i < len(tpCPP.AttributeIDs); i++ {
				mdl := &ChargerMdl{
					Tenant: tpCPP.Tenant,
					Tpid:   tpCPP.TPid,
					ID:     tpCPP.ID,
				}
				mdl.AttributeIDs = tpCPP.AttributeIDs[i]
				mdls = append(mdls, mdl)
			}
		}
	}
	return
}

func APItoChargerProfile(tpCPP *utils.TPChargerProfile, timezone string) (cpp *utils.ChargerProfile) {
	cpp = &utils.ChargerProfile{
		Tenant:       tpCPP.Tenant,
		ID:           tpCPP.ID,
		RunID:        tpCPP.RunID,
		FilterIDs:    make([]string, len(tpCPP.FilterIDs)),
		AttributeIDs: make([]string, len(tpCPP.AttributeIDs)),
	}
	if tpCPP.Weights != utils.EmptyString {
		var err error
		cpp.Weights, err = utils.NewDynamicWeightsFromString(tpCPP.Weights, utils.InfieldSep, utils.ANDSep)
		if err != nil {
			return
		}
	}
	if tpCPP.Blockers != utils.EmptyString {
		var err error
		cpp.Blockers, err = utils.NewDynamicBlockersFromString(tpCPP.Blockers, utils.InfieldSep, utils.ANDSep)
		if err != nil {
			return
		}
	}
	copy(cpp.FilterIDs, tpCPP.FilterIDs)
	copy(cpp.AttributeIDs, tpCPP.AttributeIDs)
	return cpp
}

func ChargerProfileToAPI(chargerPrf *utils.ChargerProfile) (tpCharger *utils.TPChargerProfile) {
	tpCharger = &utils.TPChargerProfile{
		Tenant:       chargerPrf.Tenant,
		ID:           chargerPrf.ID,
		FilterIDs:    make([]string, len(chargerPrf.FilterIDs)),
		Weights:      chargerPrf.Weights.String(utils.InfieldSep, utils.ANDSep),
		Blockers:     chargerPrf.Blockers.String(utils.InfieldSep, utils.ANDSep),
		RunID:        chargerPrf.RunID,
		AttributeIDs: make([]string, len(chargerPrf.AttributeIDs)),
	}
	copy(tpCharger.FilterIDs, chargerPrf.FilterIDs)
	copy(tpCharger.AttributeIDs, chargerPrf.AttributeIDs)
	return
}

// CSVHeader return the header for csv fields as a slice of string

func paramsToString(sp []any) (strategy string) {
	if len(sp) != 0 {
		strategy = sp[0].(string)
		for i := 1; i < len(sp); i++ {
			strategy += utils.InfieldSep + sp[i].(string)
		}
	}
	return
}

// RateProfileMdls is used
type RateProfileMdls []*RateProfileMdl

// CSVHeader return the header for csv fields as a slice of string
func (tps RateProfileMdls) CSVHeader() (result []string) {
	return []string{"#" + utils.Tenant, utils.ID, utils.FilterIDs,
		utils.Weights, utils.ConnectFee, utils.MinCost, utils.MaxCost, utils.MaxCostStrategy,
		utils.RateID, utils.RateFilterIDs, utils.RateActivationStart, utils.RateWeights,
		utils.RateBlocker, utils.RateIntervalStart, utils.RateFixedFee, utils.RateRecurrentFee,
		utils.RateUnit, utils.RateIncrement,
	}
}

func (tps RateProfileMdls) AsTPRateProfile() (result []*utils.TPRateProfile) {
	filterMap := make(map[string]utils.StringSet)
	mst := make(map[string]*utils.TPRateProfile)
	rateMap := make(map[string]map[string]*utils.TPRate)
	for _, tp := range tps {
		tenID := (&utils.TenantID{Tenant: tp.Tenant, ID: tp.ID}).TenantID()
		rPrf, found := mst[tenID]
		if !found {
			rPrf = &utils.TPRateProfile{
				TPid:   tp.Tpid,
				Tenant: tp.Tenant,
				ID:     tp.ID,
			}
		}
		if tp.RateID != utils.EmptyString {
			if _, has := rateMap[tenID]; !has {
				rateMap[tenID] = make(map[string]*utils.TPRate)
			}
			rate, found := rateMap[tenID][tp.RateID]
			if !found {
				rate = &utils.TPRate{
					ID:            tp.RateID,
					IntervalRates: make([]*utils.TPIntervalRate, 0),
					Blocker:       tp.RateBlocker,
				}
			}
			if tp.RateFilterIDs != utils.EmptyString {
				rateFilterSplit := strings.Split(tp.RateFilterIDs, utils.InfieldSep)
				rate.FilterIDs = append(rate.FilterIDs, rateFilterSplit...)
			}
			if tp.RateActivationTimes != utils.EmptyString {
				rate.ActivationTimes = tp.RateActivationTimes
			}
			if tp.RateWeights != utils.EmptyString {
				rate.Weights = tp.RateWeights
			}
			// create new interval rate and append to the slice
			intervalRate := new(utils.TPIntervalRate)
			if tp.RateIntervalStart != utils.EmptyString {
				intervalRate.IntervalStart = tp.RateIntervalStart
			}
			if tp.RateFixedFee != 0 {
				intervalRate.FixedFee = tp.RateFixedFee
			}
			if tp.RateRecurrentFee != 0 {
				intervalRate.RecurrentFee = tp.RateRecurrentFee
			}
			if tp.RateIncrement != utils.EmptyString {
				intervalRate.Increment = tp.RateIncrement
			}
			if tp.RateUnit != utils.EmptyString {
				intervalRate.Unit = tp.RateUnit
			}
			rate.IntervalRates = append(rate.IntervalRates, intervalRate)
			rateMap[tenID][tp.RateID] = rate
		}

		if tp.Weights != utils.EmptyString {
			rPrf.Weights = tp.Weights
		}
		if tp.MinCost != 0 {
			rPrf.MinCost = tp.MinCost
		}
		if tp.MaxCost != 0 {
			rPrf.MaxCost = tp.MaxCost
		}
		if tp.MaxCostStrategy != utils.EmptyString {
			rPrf.MaxCostStrategy = tp.MaxCostStrategy
		}
		if tp.FilterIDs != utils.EmptyString {
			if _, has := filterMap[tenID]; !has {
				filterMap[tenID] = make(utils.StringSet)
			}
			filterMap[tenID].AddSlice(strings.Split(tp.FilterIDs, utils.InfieldSep))
		}
		mst[tenID] = rPrf
	}
	result = make([]*utils.TPRateProfile, len(mst))
	i := 0
	for tntID, th := range mst {
		result[i] = th
		result[i].Rates = rateMap[tntID]
		result[i].FilterIDs = filterMap[tntID].AsSlice()
		i++
	}
	return
}

func APItoModelTPRateProfile(tPrf *utils.TPRateProfile) (mdls RateProfileMdls) {
	if len(tPrf.Rates) == 0 {
		return
	}
	i := 0
	for _, rate := range tPrf.Rates {
		for j, intervalRate := range rate.IntervalRates {
			mdl := &RateProfileMdl{
				Tenant: tPrf.Tenant,
				Tpid:   tPrf.TPid,
				ID:     tPrf.ID,
			}
			if i == 0 {
				for i, val := range tPrf.FilterIDs {
					if i != 0 {
						mdl.FilterIDs += utils.InfieldSep
					}
					mdl.FilterIDs += val
				}

				mdl.Weights = tPrf.Weights
				mdl.MinCost = tPrf.MinCost
				mdl.MaxCost = tPrf.MaxCost
				mdl.MaxCostStrategy = tPrf.MaxCostStrategy
			}
			mdl.RateID = rate.ID
			if j == 0 {
				for i, val := range rate.FilterIDs {
					if i != 0 {
						mdl.RateFilterIDs += utils.InfieldSep
					}
					mdl.RateFilterIDs += val
				}
				mdl.RateWeights = rate.Weights
				mdl.RateActivationTimes = rate.ActivationTimes
				mdl.RateBlocker = rate.Blocker

			}
			mdl.RateRecurrentFee = intervalRate.RecurrentFee
			mdl.RateFixedFee = intervalRate.FixedFee
			mdl.RateUnit = intervalRate.Unit
			mdl.RateIncrement = intervalRate.Increment
			mdl.RateIntervalStart = intervalRate.IntervalStart
			mdls = append(mdls, mdl)
			i++
		}

	}
	return
}

func APItoRateProfile(tpRp *utils.TPRateProfile, timezone string) (rp *utils.RateProfile, err error) {
	rp = &utils.RateProfile{
		Tenant:          tpRp.Tenant,
		ID:              tpRp.ID,
		FilterIDs:       make([]string, len(tpRp.FilterIDs)),
		MaxCostStrategy: tpRp.MaxCostStrategy,
		Rates:           make(map[string]*utils.Rate),
		MinCost:         utils.NewDecimalFromFloat64(tpRp.MinCost),
		MaxCost:         utils.NewDecimalFromFloat64(tpRp.MaxCost),
	}
	if tpRp.Weights != utils.EmptyString {
		weight, err := utils.NewDynamicWeightsFromString(tpRp.Weights, utils.InfieldSep, utils.ANDSep)
		if err != nil {
			return nil, err
		}
		rp.Weights = weight
	}
	copy(rp.FilterIDs, tpRp.FilterIDs)
	for key, rate := range tpRp.Rates {
		rp.Rates[key] = &utils.Rate{
			ID:              rate.ID,
			Blocker:         rate.Blocker,
			FilterIDs:       rate.FilterIDs,
			ActivationTimes: rate.ActivationTimes,
			IntervalRates:   make([]*utils.IntervalRate, len(rate.IntervalRates)),
		}
		if rate.Weights != utils.EmptyString {
			weight, err := utils.NewDynamicWeightsFromString(rate.Weights, utils.InfieldSep, utils.ANDSep)
			if err != nil {
				return nil, err
			}
			rp.Rates[key].Weights = weight
		}
		for i, iRate := range rate.IntervalRates {
			rp.Rates[key].IntervalRates[i] = new(utils.IntervalRate)
			if rp.Rates[key].IntervalRates[i].IntervalStart, err = utils.NewDecimalFromUsage(iRate.IntervalStart); err != nil {
				return nil, err
			}
			rp.Rates[key].IntervalRates[i].FixedFee = utils.NewDecimalFromFloat64(iRate.FixedFee)
			rp.Rates[key].IntervalRates[i].RecurrentFee = utils.NewDecimalFromFloat64(iRate.RecurrentFee)
			if rp.Rates[key].IntervalRates[i].Unit, err = utils.NewDecimalFromUsage(iRate.Unit); err != nil {
				return nil, err
			}
			if rp.Rates[key].IntervalRates[i].Increment, err = utils.NewDecimalFromUsage(iRate.Increment); err != nil {
				return nil, err
			}
		}
	}
	return rp, nil
}

func RateProfileToAPI(rp *utils.RateProfile) (tpRp *utils.TPRateProfile) {
	tpRp = &utils.TPRateProfile{
		Tenant:          rp.Tenant,
		ID:              rp.ID,
		FilterIDs:       make([]string, len(rp.FilterIDs)),
		Weights:         rp.Weights.String(utils.InfieldSep, utils.ANDSep),
		MaxCostStrategy: rp.MaxCostStrategy,
		Rates:           make(map[string]*utils.TPRate),
	}
	if rp.MinCost != nil {
		//there should not be an invalid value of converting from Decimal into float64
		minCostF, _ := rp.MinCost.Float64()
		tpRp.MinCost = minCostF
	}
	if rp.MaxCost != nil {
		//there should not be an invalid value of converting from Decimal into float64
		maxCostF, _ := rp.MaxCost.Float64()
		tpRp.MaxCost = maxCostF
	}

	for key, rate := range rp.Rates {
		tpRp.Rates[key] = &utils.TPRate{
			ID:              rate.ID,
			Weights:         rate.Weights.String(utils.InfieldSep, utils.ANDSep),
			Blocker:         rate.Blocker,
			FilterIDs:       rate.FilterIDs,
			ActivationTimes: rate.ActivationTimes,
			IntervalRates:   make([]*utils.TPIntervalRate, len(rate.IntervalRates)),
		}
		for i, iRate := range rate.IntervalRates {
			tpRp.Rates[key].IntervalRates[i] = &utils.TPIntervalRate{
				IntervalStart: iRate.IntervalStart.String(),
			}
			if iRate.FixedFee != nil {
				//there should not be an invalid value of converting from Decimal into float64
				fixedFeeF, _ := iRate.FixedFee.Float64()
				tpRp.Rates[key].IntervalRates[i].FixedFee = fixedFeeF
			}
			if iRate.Unit != nil {
				tpRp.Rates[key].IntervalRates[i].Unit = iRate.Unit.String()
			}
			if iRate.Increment != nil {
				tpRp.Rates[key].IntervalRates[i].Increment = iRate.Increment.String()
			}
			if iRate.RecurrentFee != nil {
				//there should not be an invalid value of converting from Decimal into float64
				recFeeF, _ := iRate.RecurrentFee.Float64()
				tpRp.Rates[key].IntervalRates[i].RecurrentFee = recFeeF
			}
		}
	}
	copy(tpRp.FilterIDs, rp.FilterIDs)
	return
}

type ActionProfileMdls []*ActionProfileMdl

// CSVHeader return the header for csv fields as a slice of string
func (apm ActionProfileMdls) CSVHeader() (result []string) {
	return []string{"#" + utils.Tenant, utils.ID, utils.FilterIDs,
		utils.Weights, utils.Blockers, utils.Schedule, utils.TargetType, utils.TargetIDs,
		utils.ActionID, utils.ActionFilterIDs, utils.ActionTTL,
		utils.ActionType, utils.ActionOpts, utils.ActionWeights, utils.ActionBlockers,
		utils.ActionDiktatsID, utils.ActionDiktatsFilterIDs, utils.ActionDiktatsOpts,
		utils.ActionDiktatsWeights, utils.ActionDiktatsBlockers,
	}
}

func (apm ActionProfileMdls) AsTPActionProfile() (result []*utils.TPActionProfile) {
	filterIDsMap := make(map[string]utils.StringSet)
	targetIDsMap := make(map[string]map[string]utils.StringSet)
	actPrfMap := make(map[string]*utils.TPActionProfile)
	for _, tp := range apm {
		tenID := (&utils.TenantID{Tenant: tp.Tenant, ID: tp.ID}).TenantID()
		aPrf, found := actPrfMap[tenID]
		if !found {
			aPrf = &utils.TPActionProfile{
				TPid:   tp.Tpid,
				Tenant: tp.Tenant,
				ID:     tp.ID,
			}
		}
		if tp.FilterIDs != utils.EmptyString {
			if _, has := filterIDsMap[tenID]; !has {
				filterIDsMap[tenID] = make(utils.StringSet)
			}
			filterIDsMap[tenID].AddSlice(strings.Split(tp.FilterIDs, utils.InfieldSep))
		}
		if tp.Weights != utils.EmptyString {
			aPrf.Weights = tp.Weights
		}
		if tp.Blockers != utils.EmptyString {
			aPrf.Blockers = tp.Blockers
		}
		if tp.Schedule != utils.EmptyString {
			aPrf.Schedule = tp.Schedule
		}
		if tp.TargetType != utils.EmptyString {
			if _, has := targetIDsMap[tenID]; !has {
				targetIDsMap[tenID] = make(map[string]utils.StringSet)
			}
			targetIDsMap[tenID][tp.TargetType] = utils.NewStringSet(strings.Split(tp.TargetIDs, utils.InfieldSep))
		}

		if tp.ActionID != utils.EmptyString {
			var tpAAction *utils.TPAPAction
			if lacts := len(aPrf.Actions); lacts == 0 ||
				aPrf.Actions[lacts-1].ID != tp.ActionID {
				tpAAction = &utils.TPAPAction{
					ID:   tp.ActionID,
					TTL:  tp.ActionTTL,
					Type: tp.ActionType,
					Opts: tp.ActionOpts,
					Diktats: []*utils.TPAPDiktat{{
						ID:   tp.ActionDiktatsID,
						Opts: tp.ActionDiktatsOpts,
					}},
				}
				if tp.ActionFilterIDs != utils.EmptyString {
					tpAAction.FilterIDs = utils.NewStringSet(strings.Split(tp.ActionFilterIDs, utils.InfieldSep)).AsSlice()
				}
				if tp.ActionWeights != utils.EmptyString {
					tpAAction.Weights = tp.ActionWeights
				}
				if tp.ActionBlockers != utils.EmptyString {
					tpAAction.Blockers = tp.ActionBlockers
				}
				if tp.ActionDiktatsFilterIDs != utils.EmptyString {
					tpAAction.Diktats[0].FilterIDs = utils.NewStringSet(strings.Split(tp.ActionDiktatsFilterIDs, utils.InfieldSep)).AsSlice()
				}
				if tp.ActionDiktatsWeights != utils.EmptyString {
					tpAAction.Diktats[0].Weights = tp.ActionDiktatsWeights
				}
				if tp.ActionDiktatsBlockers != utils.EmptyString {
					tpAAction.Diktats[0].Blockers = tp.ActionDiktatsBlockers
				}
				aPrf.Actions = append(aPrf.Actions, tpAAction)
			} else {
				diktat := &utils.TPAPDiktat{
					ID:   tp.ActionDiktatsID,
					Opts: tp.ActionDiktatsOpts,
				}
				if tp.ActionDiktatsFilterIDs != utils.EmptyString {
					diktat.FilterIDs = utils.NewStringSet(strings.Split(tp.ActionDiktatsFilterIDs, utils.InfieldSep)).AsSlice()
				}
				if tp.ActionDiktatsWeights != utils.EmptyString {
					diktat.Weights = tp.ActionDiktatsWeights
				}
				if tp.ActionDiktatsBlockers != utils.EmptyString {
					diktat.Blockers = tp.ActionDiktatsBlockers
				}
				aPrf.Actions[lacts-1].Diktats = append(aPrf.Actions[lacts-1].Diktats,
					diktat)
			}
		}
		actPrfMap[tenID] = aPrf
	}
	result = make([]*utils.TPActionProfile, len(actPrfMap))
	i := 0
	for tntID, th := range actPrfMap {
		result[i] = th
		result[i].FilterIDs = filterIDsMap[tntID].AsSlice()
		for targetType, targetIDs := range targetIDsMap[tntID] {
			result[i].Targets = append(result[i].Targets, &utils.TPActionTarget{TargetType: targetType, TargetIDs: targetIDs.AsSlice()})
		}
		i++
	}
	return
}

func APItoModelTPActionProfile(tPrf *utils.TPActionProfile) (mdls ActionProfileMdls) {
	if len(tPrf.Actions) == 0 {
		return
	}
	for i, action := range tPrf.Actions {
		mdl := &ActionProfileMdl{
			Tenant: tPrf.Tenant,
			Tpid:   tPrf.TPid,
			ID:     tPrf.ID,
		}
		if i == 0 {
			mdl.FilterIDs = strings.Join(tPrf.FilterIDs, utils.InfieldSep)
			mdl.Weights = tPrf.Weights
			mdl.Blockers = tPrf.Blockers
			mdl.Schedule = tPrf.Schedule
			for _, target := range tPrf.Targets {
				mdl.TargetType = target.TargetType
				mdl.TargetIDs = strings.Join(target.TargetIDs, utils.InfieldSep)
			}
		}
		mdl.ActionID = action.ID
		mdl.ActionFilterIDs = strings.Join(action.FilterIDs, utils.InfieldSep)
		mdl.ActionTTL = action.TTL
		mdl.ActionType = action.Type
		mdl.ActionOpts = action.Opts
		mdl.ActionWeights = action.Weights
		mdl.ActionBlockers = action.Blockers
		for j, actD := range action.Diktats {
			if j != 0 {
				mdl = &ActionProfileMdl{
					Tenant:     mdl.Tenant,
					Tpid:       mdl.Tpid,
					ID:         mdl.ID,
					ActionID:   mdl.ActionID,
					ActionType: mdl.ActionType,
				}
			}
			mdl.ActionDiktatsID = actD.ID
			mdl.ActionDiktatsFilterIDs = strings.Join(actD.FilterIDs, utils.InfieldSep)
			mdl.ActionDiktatsOpts = actD.Opts
			mdl.ActionDiktatsWeights = actD.Weights
			mdl.ActionDiktatsBlockers = actD.Blockers
		}
		mdls = append(mdls, mdl)
	}
	return
}

func APItoActionProfile(tpAp *utils.TPActionProfile, timezone string) (ap *utils.ActionProfile, err error) {
	ap = &utils.ActionProfile{
		Tenant:    tpAp.Tenant,
		ID:        tpAp.ID,
		FilterIDs: make([]string, len(tpAp.FilterIDs)),
		Schedule:  tpAp.Schedule,
		Targets:   make(map[string]utils.StringSet),
		Actions:   make([]*utils.APAction, len(tpAp.Actions)),
	}
	if tpAp.Weights != utils.EmptyString {
		if ap.Weights, err = utils.NewDynamicWeightsFromString(tpAp.Weights, utils.InfieldSep, utils.ANDSep); err != nil {
			return
		}
	}
	if tpAp.Blockers != utils.EmptyString {
		if ap.Blockers, err = utils.NewDynamicBlockersFromString(tpAp.Blockers, utils.InfieldSep, utils.ANDSep); err != nil {
			return
		}
	}
	copy(ap.FilterIDs, tpAp.FilterIDs)
	for _, target := range tpAp.Targets {
		ap.Targets[target.TargetType] = utils.NewStringSet(target.TargetIDs)
	}
	for i, act := range tpAp.Actions {
		actDs := make([]*utils.APDiktat, len(act.Diktats))
		for j, actD := range act.Diktats {
			if actD.ID == utils.EmptyString {
				return nil, fmt.Errorf("missing ID from Diktats of ActionProfile <%s> Action <%s>", ap.TenantID(), actD.ID)
			}
			actDs[j] = &utils.APDiktat{
				ID:        actD.ID,
				FilterIDs: actD.FilterIDs,
			}
			if actD.Opts != utils.EmptyString {
				actDs[j].Opts = make(map[string]any)
				for opt := range strings.SplitSeq(actD.Opts, utils.InfieldSep) { // example of opts: key1:val1;key2:val2;key3:val3
					keyValSls := utils.SplitPath(opt, utils.InInFieldSep[0], 2)
					if len(keyValSls) != 2 {
						return nil, fmt.Errorf("malformed option for ActionProfile <%s> for action <%s> for diktat <%s>", ap.TenantID(), actD.ID, actD.ID)

					}
					actDs[j].Opts[keyValSls[0]] = keyValSls[1]
				}
			}
			if actD.Weights != utils.EmptyString {
				if actDs[j].Weights, err = utils.NewDynamicWeightsFromString(actD.Weights, utils.InfieldSep, utils.ANDSep); err != nil {
					return
				}
			}
			if actD.Blockers != utils.EmptyString {
				if actDs[j].Blockers, err = utils.NewDynamicBlockersFromString(actD.Blockers, utils.InfieldSep, utils.ANDSep); err != nil {
					return
				}
			}
		}
		ap.Actions[i] = &utils.APAction{
			ID:        act.ID,
			FilterIDs: act.FilterIDs,
			Type:      act.Type,
			Diktats:   actDs,
		}
		if ap.Actions[i].TTL, err = utils.ParseDurationWithNanosecs(act.TTL); err != nil {
			return
		}
		if act.Opts != utils.EmptyString {
			ap.Actions[i].Opts = make(map[string]any)
			for opt := range strings.SplitSeq(act.Opts, utils.InfieldSep) { // example of opts: key1:val1;key2:val2;key3:val3
				keyValSls := utils.SplitPath(opt, utils.InInFieldSep[0], 2)
				if len(keyValSls) != 2 {
					err = fmt.Errorf("malformed option for ActionProfile <%s> for action <%s>", ap.TenantID(), act.ID)
					return
				}
				ap.Actions[i].Opts[keyValSls[0]] = keyValSls[1]
			}
		}
		if act.Weights != utils.EmptyString {
			if ap.Actions[i].Weights, err = utils.NewDynamicWeightsFromString(act.Weights, utils.InfieldSep, utils.ANDSep); err != nil {
				return
			}
		}
		if act.Blockers != utils.EmptyString {
			if ap.Actions[i].Blockers, err = utils.NewDynamicBlockersFromString(act.Blockers, utils.InfieldSep, utils.ANDSep); err != nil {
				return
			}
		}
	}
	return
}

func ActionProfileToAPI(ap *utils.ActionProfile) (tpAp *utils.TPActionProfile) {
	tpAp = &utils.TPActionProfile{
		Tenant:    ap.Tenant,
		ID:        ap.ID,
		FilterIDs: make([]string, len(ap.FilterIDs)),
		Weights:   ap.Weights.String(utils.InfieldSep, utils.ANDSep),
		Blockers:  ap.Blockers.String(utils.InfieldSep, utils.ANDSep),
		Schedule:  ap.Schedule,
		Targets:   make([]*utils.TPActionTarget, 0, len(ap.Targets)),
		Actions:   make([]*utils.TPAPAction, len(ap.Actions)),
	}
	copy(tpAp.FilterIDs, ap.FilterIDs)
	for targetType, targetIDs := range ap.Targets {
		tpAp.Targets = append(tpAp.Targets, &utils.TPActionTarget{TargetType: targetType, TargetIDs: targetIDs.AsSlice()})
	}
	for i, act := range ap.Actions {
		actDs := make([]*utils.TPAPDiktat, len(act.Diktats))
		for j, actD := range act.Diktats {
			elems := make([]string, 0, len(actD.Opts))
			for k, v := range actD.Opts {
				elems = append(elems, utils.ConcatenatedKey(k, utils.IfaceAsString(v)))
			}
			actDs[j] = &utils.TPAPDiktat{
				ID:        actD.ID,
				FilterIDs: actD.FilterIDs,
				Opts:      strings.Join(elems, utils.InfieldSep),
				Weights:   actD.Weights.String(utils.InfieldSep, utils.ANDSep),
				Blockers:  actD.Blockers.String(utils.InfieldSep, utils.ANDSep),
			}
		}

		elems := make([]string, 0, len(act.Opts))
		for k, v := range act.Opts {
			elems = append(elems, utils.ConcatenatedKey(k, utils.IfaceAsString(v)))
		}
		tpAp.Actions[i] = &utils.TPAPAction{
			ID:        act.ID,
			FilterIDs: act.FilterIDs,
			TTL:       act.TTL.String(),
			Type:      act.Type,
			Opts:      strings.Join(elems, utils.InfieldSep),
			Weights:   act.Weights.String(utils.InfieldSep, utils.ANDSep),
			Blockers:  act.Blockers.String(utils.InfieldSep, utils.ANDSep),
			Diktats:   actDs,
		}
	}
	return
}

type AccountMdls []*AccountMdl

// CSVHeader return the header for csv fields as a slice of string
func (apm AccountMdls) CSVHeader() (result []string) {
	return []string{"#" + utils.Tenant, utils.ID, utils.FilterIDs,
		utils.Weights, utils.Blockers, utils.Opts, utils.BalanceID, utils.BalanceFilterIDs, utils.BalanceWeights, utils.BalanceBlockers, utils.BalanceType, utils.BalanceUnits, utils.BalanceUnitFactors, utils.BalanceOpts, utils.BalanceCostIncrements, utils.BalanceAttributeIDs, utils.BalanceRateProfileIDs,
		utils.ThresholdIDs}
}

func (apm AccountMdls) AsTPAccount() (result []*utils.TPAccount, err error) {
	filterIDsMap := make(map[string]utils.StringSet)
	thresholdIDsMap := make(map[string]utils.StringSet)
	actPrfMap := make(map[string]*utils.TPAccount)
	for _, tp := range apm {
		tenID := (&utils.TenantID{Tenant: tp.Tenant, ID: tp.ID}).TenantID()
		aPrf, found := actPrfMap[tenID]
		if !found {
			aPrf = &utils.TPAccount{
				TPid:     tp.Tpid,
				Tenant:   tp.Tenant,
				ID:       tp.ID,
				Weights:  tp.Weights,
				Blockers: tp.Blockers,
				Balances: make(map[string]*utils.TPAccountBalance),
			}
		}
		if tp.FilterIDs != utils.EmptyString {
			if _, has := filterIDsMap[tenID]; !has {
				filterIDsMap[tenID] = make(utils.StringSet)
			}
			filterIDsMap[tenID].AddSlice(strings.Split(tp.FilterIDs, utils.InfieldSep))
		}
		if tp.ThresholdIDs != utils.EmptyString {
			if _, has := thresholdIDsMap[tenID]; !has {
				thresholdIDsMap[tenID] = make(utils.StringSet)
			}
			thresholdIDsMap[tenID].AddSlice(strings.Split(tp.ThresholdIDs, utils.InfieldSep))
		}
		if tp.BalanceID != utils.EmptyString {
			aPrf.Balances[tp.BalanceID] = &utils.TPAccountBalance{
				ID:       tp.BalanceID,
				Weights:  tp.BalanceWeights,
				Blockers: tp.BalanceBlockers,
				Type:     tp.BalanceType,
				Opts:     tp.BalanceOpts,
				Units:    tp.BalanceUnits,
			}

			if tp.BalanceFilterIDs != utils.EmptyString {
				aPrf.Balances[tp.BalanceID].FilterIDs = utils.NewStringSet(strings.Split(tp.BalanceFilterIDs, utils.InfieldSep)).AsSlice()
			}
			// cost increment mdl: fltr1&fltr2;incr;fixed;recurrent
			if tp.BalanceCostIncrements != utils.EmptyString {
				costIncrements := make([]*utils.TPBalanceCostIncrement, 0)
				sls := strings.Split(tp.BalanceCostIncrements, utils.InfieldSep)
				if len(sls)%4 != 0 {
					return nil, fmt.Errorf("invalid key: <%s> for BalanceCostIncrements", tp.BalanceCostIncrements)
				}
				for j := 0; j < len(sls); j = j + 4 {
					costIncrement, err := utils.NewTPBalanceCostIncrement(sls[j], sls[j+1], sls[j+2], sls[j+3])
					if err != nil {
						return nil, err
					}
					costIncrements = append(costIncrements, costIncrement)
				}
				aPrf.Balances[tp.BalanceID].CostIncrement = costIncrements
			}
			if tp.BalanceAttributeIDs != utils.EmptyString {
				// the order for attributes is important
				// also no duplicate check as we would
				// need to let the user execute the same
				// attribute twice if needed
				aPrf.Balances[tp.BalanceID].AttributeIDs = strings.Split(tp.BalanceAttributeIDs, utils.InfieldSep)
			}
			if tp.BalanceRateProfileIDs != utils.EmptyString {
				aPrf.Balances[tp.BalanceID].RateProfileIDs = utils.NewStringSet(strings.Split(tp.BalanceRateProfileIDs, utils.InfieldSep)).AsSlice()
			}
			if tp.BalanceUnitFactors != utils.EmptyString {
				unitFactors := make([]*utils.TPBalanceUnitFactor, 0)
				sls := strings.Split(tp.BalanceUnitFactors, utils.InfieldSep)
				if len(sls)%2 != 0 {
					return nil, fmt.Errorf("invalid key: <%s> for BalanceUnitFactors", tp.BalanceUnitFactors)
				}

				for j := 0; j < len(sls); j = j + 2 {
					unitFactor, err := utils.NewTPBalanceUnitFactor(sls[j], sls[j+1])
					if err != nil {
						return nil, err
					}
					unitFactors = append(unitFactors, unitFactor)
				}
				aPrf.Balances[tp.BalanceID].UnitFactors = unitFactors
			}

		}
		actPrfMap[tenID] = aPrf
	}
	result = make([]*utils.TPAccount, len(actPrfMap))
	i := 0
	for tntID, th := range actPrfMap {
		result[i] = th
		result[i].FilterIDs = filterIDsMap[tntID].AsSlice()
		result[i].ThresholdIDs = thresholdIDsMap[tntID].AsSlice()
		i++
	}
	return
}

func APItoModelTPAccount(tPrf *utils.TPAccount) (mdls AccountMdls) {
	if len(tPrf.Balances) == 0 {
		return
	}
	i := 0
	for _, balance := range tPrf.Balances {
		mdl := &AccountMdl{
			Tenant: tPrf.Tenant,
			Tpid:   tPrf.TPid,
			ID:     tPrf.ID,
		}
		if i == 0 {
			for i, val := range tPrf.FilterIDs {
				if i != 0 {
					mdl.FilterIDs += utils.InfieldSep
				}
				mdl.FilterIDs += val
			}
			for i, val := range tPrf.ThresholdIDs {
				if i != 0 {
					mdl.ThresholdIDs += utils.InfieldSep
				}
				mdl.ThresholdIDs += val
			}
			mdl.Weights = tPrf.Weights
			mdl.Blockers = tPrf.Blockers
		}
		mdl.BalanceID = balance.ID
		for i, val := range balance.FilterIDs {
			if i != 0 {
				mdl.BalanceFilterIDs += utils.InfieldSep
			}
			mdl.BalanceFilterIDs += val
		}
		mdl.BalanceWeights = balance.Weights
		mdl.BalanceBlockers = balance.Blockers
		mdl.BalanceType = balance.Type
		mdl.BalanceOpts = balance.Opts
		for i, costIncr := range balance.CostIncrement {
			if i != 0 {
				mdl.BalanceCostIncrements += utils.InfieldSep
			}
			mdl.BalanceCostIncrements += costIncr.AsString()
		}
		for i, attrID := range balance.AttributeIDs {
			if i != 0 {
				mdl.BalanceAttributeIDs += utils.InfieldSep
			}
			mdl.BalanceAttributeIDs += attrID
		}
		for i, ratePrfID := range balance.RateProfileIDs {
			if i != 0 {
				mdl.BalanceRateProfileIDs += utils.InfieldSep
			}
			mdl.BalanceRateProfileIDs += ratePrfID
		}
		for i, unitFactor := range balance.UnitFactors {
			if i != 0 {
				mdl.BalanceUnitFactors += utils.InfieldSep
			}
			mdl.BalanceUnitFactors += unitFactor.AsString()
		}
		mdl.BalanceUnits = balance.Units
		mdls = append(mdls, mdl)
		i++
	}
	return
}

func APItoAccount(tpAcc *utils.TPAccount, timezone string) (acc *utils.Account, err error) {
	acc = &utils.Account{
		Tenant:       tpAcc.Tenant,
		ID:           tpAcc.ID,
		FilterIDs:    make([]string, len(tpAcc.FilterIDs)),
		Balances:     make(map[string]*utils.Balance, len(tpAcc.Balances)),
		ThresholdIDs: make([]string, len(tpAcc.ThresholdIDs)),
	}
	if tpAcc.Weights != utils.EmptyString {
		weight, err := utils.NewDynamicWeightsFromString(tpAcc.Weights, utils.InfieldSep, utils.ANDSep)
		if err != nil {
			return nil, err
		}
		acc.Weights = weight
	}
	if tpAcc.Blockers != utils.EmptyString {
		blockers, err := utils.NewDynamicBlockersFromString(tpAcc.Blockers, utils.InfieldSep, utils.ANDSep)
		if err != nil {
			return nil, err
		}
		acc.Blockers = blockers
	}
	copy(acc.FilterIDs, tpAcc.FilterIDs)
	for id, bal := range tpAcc.Balances {
		acc.Balances[id] = &utils.Balance{
			ID:        bal.ID,
			FilterIDs: bal.FilterIDs,
			Type:      bal.Type,
		}
		if bal.Units != utils.EmptyString {
			units, err := utils.NewDecimalFromUsage(bal.Units)
			if err != nil {
				return nil, err
			}
			acc.Balances[id].Units = units
		}
		if bal.Weights != utils.EmptyString {
			weights, err := utils.NewDynamicWeightsFromString(bal.Weights, utils.InfieldSep, utils.ANDSep)
			if err != nil {
				return nil, err
			}
			acc.Balances[id].Weights = weights
		}
		if bal.Blockers != utils.EmptyString {
			blockers, err := utils.NewDynamicBlockersFromString(bal.Blockers, utils.InfieldSep, utils.ANDSep)
			if err != nil {
				return nil, err
			}
			acc.Balances[id].Blockers = blockers
		}
		if bal.UnitFactors != nil {
			acc.Balances[id].UnitFactors = make([]*utils.UnitFactor, len(bal.UnitFactors))
			for j, unitFactor := range bal.UnitFactors {
				acc.Balances[id].UnitFactors[j] = &utils.UnitFactor{
					FilterIDs: unitFactor.FilterIDs,
					Factor:    utils.NewDecimalFromFloat64(unitFactor.Factor),
				}
			}
		}
		if bal.Opts != utils.EmptyString {
			acc.Balances[id].Opts = make(map[string]any)
			for _, opt := range strings.Split(bal.Opts, utils.InfieldSep) { // example of opts: key1:val1;key2:val2;key3:val3
				keyValSls := utils.SplitConcatenatedKey(opt)
				if len(keyValSls) != 2 {
					err = fmt.Errorf("malformed option for ActionProfile <%s> for action <%s>", acc.TenantID(), bal.ID)
					return
				}
				acc.Balances[id].Opts[keyValSls[0]] = keyValSls[1]
			}
		}
		if bal.CostIncrement != nil {
			acc.Balances[id].CostIncrements = make([]*utils.CostIncrement, len(bal.CostIncrement))
			for j, costIncrement := range bal.CostIncrement {
				acc.Balances[id].CostIncrements[j] = &utils.CostIncrement{
					FilterIDs: costIncrement.FilterIDs,
				}
				if costIncrement.Increment != utils.EmptyString {
					acc.Balances[id].CostIncrements[j].Increment, err = utils.NewDecimalFromUsage(costIncrement.Increment)
				}
				if costIncrement.FixedFee != nil {
					acc.Balances[id].CostIncrements[j].FixedFee = utils.NewDecimalFromFloat64(*costIncrement.FixedFee)
				}
				if costIncrement.RecurrentFee != nil {
					acc.Balances[id].CostIncrements[j].RecurrentFee = utils.NewDecimalFromFloat64(*costIncrement.RecurrentFee)
				}
			}
		}
		if bal.AttributeIDs != nil {
			acc.Balances[id].AttributeIDs = make([]string, len(bal.AttributeIDs))
			copy(acc.Balances[id].AttributeIDs, bal.AttributeIDs)
		}
		if bal.RateProfileIDs != nil {
			acc.Balances[id].RateProfileIDs = make([]string, len(bal.RateProfileIDs))
			copy(acc.Balances[id].RateProfileIDs, bal.RateProfileIDs)
		}
	}
	copy(acc.ThresholdIDs, tpAcc.ThresholdIDs)
	return
}

func AccountToAPI(acc *utils.Account) (tpAcc *utils.TPAccount) {
	tpAcc = &utils.TPAccount{
		Tenant:       acc.Tenant,
		ID:           acc.ID,
		Weights:      acc.Weights.String(utils.InfieldSep, utils.ANDSep),
		Blockers:     acc.Blockers.String(utils.InfieldSep, utils.ANDSep),
		FilterIDs:    make([]string, len(acc.FilterIDs)),
		Balances:     make(map[string]*utils.TPAccountBalance, len(acc.Balances)),
		ThresholdIDs: make([]string, len(acc.ThresholdIDs)),
	}
	copy(tpAcc.FilterIDs, acc.FilterIDs)
	for i, bal := range acc.Balances {
		tpAcc.Balances[i] = &utils.TPAccountBalance{
			ID:             bal.ID,
			FilterIDs:      make([]string, len(bal.FilterIDs)),
			Weights:        bal.Weights.String(utils.InfieldSep, utils.ANDSep),
			Blockers:       bal.Blockers.String(utils.InfieldSep, utils.ANDSep),
			Type:           bal.Type,
			Units:          bal.Units.String(),
			CostIncrement:  make([]*utils.TPBalanceCostIncrement, len(bal.CostIncrements)),
			AttributeIDs:   make([]string, len(bal.AttributeIDs)),
			RateProfileIDs: make([]string, len(bal.RateProfileIDs)),
			UnitFactors:    make([]*utils.TPBalanceUnitFactor, len(bal.UnitFactors)),
		}
		copy(tpAcc.Balances[i].FilterIDs, bal.FilterIDs)
		//there should not be an invalid value of converting into float64
		elems := make([]string, 0, len(bal.Opts))
		for k, v := range bal.Opts {
			elems = append(elems, utils.ConcatenatedKey(k, utils.IfaceAsString(v)))
		}
		for k, cIncrement := range bal.CostIncrements {
			tpAcc.Balances[i].CostIncrement[k] = &utils.TPBalanceCostIncrement{
				FilterIDs: make([]string, len(cIncrement.FilterIDs)),
				Increment: cIncrement.Increment.String(),
			}
			copy(tpAcc.Balances[i].CostIncrement[k].FilterIDs, cIncrement.FilterIDs)
			if cIncrement.FixedFee != nil {
				//there should not be an invalid value of converting from Decimal into float64
				fxdFee, _ := cIncrement.FixedFee.Float64()
				tpAcc.Balances[i].CostIncrement[k].FixedFee = &fxdFee
			}
			if cIncrement.RecurrentFee != nil {
				//there should not be an invalid value of converting from Decimal into float64
				rcrFee, _ := cIncrement.RecurrentFee.Float64()
				tpAcc.Balances[i].CostIncrement[k].RecurrentFee = &rcrFee
			}
		}
		copy(tpAcc.Balances[i].AttributeIDs, bal.AttributeIDs)
		copy(tpAcc.Balances[i].RateProfileIDs, bal.RateProfileIDs)
		for k, uFactor := range bal.UnitFactors {
			tpAcc.Balances[i].UnitFactors[k] = &utils.TPBalanceUnitFactor{
				FilterIDs: make([]string, len(uFactor.FilterIDs)),
			}
			copy(tpAcc.Balances[i].UnitFactors[k].FilterIDs, uFactor.FilterIDs)
			if uFactor.Factor != nil {
				//there should not be an invalid value of converting from Decimal into float64
				untFctr, _ := uFactor.Factor.Float64()
				tpAcc.Balances[i].UnitFactors[k].Factor = untFctr
			}
		}
		tpAcc.Balances[i].Opts = strings.Join(elems, utils.InfieldSep)
	}
	copy(tpAcc.ThresholdIDs, acc.ThresholdIDs)
	return
}
