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

import (
	"slices"
	"time"

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

// FsConnCfg one connection to FreeSWITCH server
type FsConnCfg struct {
	Address              string
	Password             string
	Reconnects           int
	MaxReconnectInterval time.Duration
	ReplyTimeout         time.Duration
	Alias                string
}

func (fs *FsConnCfg) loadFromJSONCfg(jsnCfg *FsConnJsonCfg) (err error) {
	if jsnCfg == nil {
		return
	}
	if jsnCfg.Address != nil {
		fs.Address = *jsnCfg.Address
	}
	if jsnCfg.Password != nil {
		fs.Password = *jsnCfg.Password
	}
	if jsnCfg.Reconnects != nil {
		fs.Reconnects = *jsnCfg.Reconnects
	}
	if jsnCfg.MaxReconnectInterval != nil {
		if fs.MaxReconnectInterval, err = utils.ParseDurationWithNanosecs(*jsnCfg.MaxReconnectInterval); err != nil {
			return
		}
	}
	if jsnCfg.ReplyTimeout != nil {
		if fs.ReplyTimeout, err = utils.ParseDurationWithNanosecs(*jsnCfg.ReplyTimeout); err != nil {
			return
		}
	}
	fs.Alias = fs.Address
	if jsnCfg.Alias != nil && *jsnCfg.Alias != "" {
		fs.Alias = *jsnCfg.Alias
	}

	return
}

// AsMapInterface returns the config as a map[string]any
func (fs FsConnCfg) AsMapInterface() map[string]any {
	return map[string]any{
		utils.AddressCfg:              fs.Address,
		utils.Password:                fs.Password,
		utils.ReconnectsCfg:           fs.Reconnects,
		utils.MaxReconnectIntervalCfg: fs.MaxReconnectInterval.String(),
		utils.ReplyTimeoutCfg:         fs.ReplyTimeout.String(),
		utils.AliasCfg:                fs.Alias,
	}
}

// Clone returns a deep copy of AsteriskAgentCfg
func (fs FsConnCfg) Clone() *FsConnCfg {
	return &FsConnCfg{
		Address:              fs.Address,
		Password:             fs.Password,
		Reconnects:           fs.Reconnects,
		MaxReconnectInterval: fs.MaxReconnectInterval,
		ReplyTimeout:         fs.ReplyTimeout,
		Alias:                fs.Alias,
	}
}

// FsAgentCfg the config section that describes the FreeSWITCH Agent
type FsAgentCfg struct {
	Enabled                bool
	SessionSConns          []string
	SubscribePark          bool
	CreateCDR              bool
	ExtraFields            utils.RSRParsers
	LowBalanceAnnFile      string
	EmptyBalanceContext    string
	EmptyBalanceAnnFile    string
	MaxWaitConnection      time.Duration
	ActiveSessionDelimiter string
	EventSocketConns       []*FsConnCfg
	RequestProcessors      []*RequestProcessor
}

// loadFreeswitchAgentCfg loads the FreeswitchAgent section of the configuration
func (fscfg *FsAgentCfg) Load(ctx *context.Context, jsnCfg ConfigDB, _ *CGRConfig) (err error) {
	jsnSmFsCfg := new(FreeswitchAgentJsonCfg)
	if err = jsnCfg.GetSection(ctx, FreeSWITCHAgentJSON, jsnSmFsCfg); err != nil {
		return
	}
	return fscfg.loadFromJSONCfg(jsnSmFsCfg)
}

func (fscfg *FsAgentCfg) loadFromJSONCfg(jsnCfg *FreeswitchAgentJsonCfg) error {
	if jsnCfg == nil {
		return nil
	}
	var err error
	if jsnCfg.Enabled != nil {
		fscfg.Enabled = *jsnCfg.Enabled
	}
	if jsnCfg.SessionSConns != nil {
		fscfg.SessionSConns = tagInternalConns(*jsnCfg.SessionSConns, utils.MetaSessionS)
	}
	if jsnCfg.SubscribePark != nil {
		fscfg.SubscribePark = *jsnCfg.SubscribePark
	}
	if jsnCfg.CreateCDR != nil {
		fscfg.CreateCDR = *jsnCfg.CreateCDR
	}
	if jsnCfg.ExtraFields != nil {
		if fscfg.ExtraFields, err = utils.NewRSRParsersFromSlice(*jsnCfg.ExtraFields); err != nil {
			return err
		}
	}
	if jsnCfg.LowBalanceAnnFile != nil {
		fscfg.LowBalanceAnnFile = *jsnCfg.LowBalanceAnnFile
	}
	if jsnCfg.EmptyBalanceContext != nil {
		fscfg.EmptyBalanceContext = *jsnCfg.EmptyBalanceContext
	}

	if jsnCfg.EmptyBalanceAnnFile != nil {
		fscfg.EmptyBalanceAnnFile = *jsnCfg.EmptyBalanceAnnFile
	}
	if jsnCfg.MaxWaitConnection != nil {
		if fscfg.MaxWaitConnection, err = utils.ParseDurationWithNanosecs(*jsnCfg.MaxWaitConnection); err != nil {
			return err
		}
	}
	if jsnCfg.ActiveSessionDelimiter != nil {
		fscfg.ActiveSessionDelimiter = *jsnCfg.ActiveSessionDelimiter
	}
	if jsnCfg.EventSocketConns != nil {
		fscfg.EventSocketConns = make([]*FsConnCfg, len(*jsnCfg.EventSocketConns))
		for idx, jsnConnCfg := range *jsnCfg.EventSocketConns {
			fscfg.EventSocketConns[idx] = getDftFsConnCfg()
			fscfg.EventSocketConns[idx].loadFromJSONCfg(jsnConnCfg)
		}
	}
	fscfg.RequestProcessors, err = appendRequestProcessors(fscfg.RequestProcessors, jsnCfg.Request_processors)
	return nil
}

// AsMapInterface returns the config as a map[string]any
func (fscfg FsAgentCfg) AsMapInterface() any {
	mp := map[string]any{
		utils.EnabledCfg:                fscfg.Enabled,
		utils.SubscribeParkCfg:          fscfg.SubscribePark,
		utils.CreateCdrCfg:              fscfg.CreateCDR,
		utils.LowBalanceAnnFileCfg:      fscfg.LowBalanceAnnFile,
		utils.EmptyBalanceContextCfg:    fscfg.EmptyBalanceContext,
		utils.EmptyBalanceAnnFileCfg:    fscfg.EmptyBalanceAnnFile,
		utils.MaxWaitConnectionCfg:      utils.EmptyString,
		utils.ActiveSessionDelimiterCfg: fscfg.ActiveSessionDelimiter,
	}
	if fscfg.SessionSConns != nil {
		mp[utils.SessionSConnsCfg] = stripInternalConns(fscfg.SessionSConns)
	}
	requestProcessors := make([]map[string]any, len(fscfg.RequestProcessors))
	for i, item := range fscfg.RequestProcessors {
		requestProcessors[i] = item.AsMapInterface()
	}
	mp[utils.RequestProcessorsCfg] = requestProcessors
	if fscfg.ExtraFields != nil {
		mp[utils.ExtraFieldsCfg] = fscfg.ExtraFields.AsStringSlice()
	}

	if fscfg.MaxWaitConnection != 0 {
		mp[utils.MaxWaitConnectionCfg] = fscfg.MaxWaitConnection.String()
	}
	if fscfg.EventSocketConns != nil {
		eventSocketConns := make([]map[string]any, len(fscfg.EventSocketConns))
		for key, item := range fscfg.EventSocketConns {
			eventSocketConns[key] = item.AsMapInterface()
		}
		mp[utils.EventSocketConnsCfg] = eventSocketConns
	}
	return mp
}

func (FsAgentCfg) SName() string               { return FreeSWITCHAgentJSON }
func (fscfg FsAgentCfg) CloneSection() Section { return fscfg.Clone() }

// Clone returns a deep copy of FsAgentCfg
func (fscfg FsAgentCfg) Clone() (cln *FsAgentCfg) {
	cln = &FsAgentCfg{
		Enabled:                fscfg.Enabled,
		SubscribePark:          fscfg.SubscribePark,
		CreateCDR:              fscfg.CreateCDR,
		ExtraFields:            fscfg.ExtraFields.Clone(),
		LowBalanceAnnFile:      fscfg.LowBalanceAnnFile,
		EmptyBalanceContext:    fscfg.EmptyBalanceContext,
		EmptyBalanceAnnFile:    fscfg.EmptyBalanceAnnFile,
		MaxWaitConnection:      fscfg.MaxWaitConnection,
		ActiveSessionDelimiter: fscfg.ActiveSessionDelimiter,
		SessionSConns:          slices.Clone(fscfg.SessionSConns),
	}
	if fscfg.EventSocketConns != nil {
		cln.EventSocketConns = make([]*FsConnCfg, len(fscfg.EventSocketConns))
		for i, req := range fscfg.EventSocketConns {
			cln.EventSocketConns[i] = req.Clone()
		}
	}
	if fscfg.RequestProcessors != nil {
		cln.RequestProcessors = make([]*RequestProcessor, len(fscfg.RequestProcessors))
		for i, req := range fscfg.RequestProcessors {
			cln.RequestProcessors[i] = req.Clone()
		}
	}
	return
}

// FreeSWITCHAgent config section
type FreeswitchAgentJsonCfg struct {
	Enabled                *bool             `json:"enabled"`
	SessionSConns          *[]string         `json:"sessions_conns"`
	SubscribePark          *bool             `json:"subscribe_park"`
	CreateCDR              *bool             `json:"create_cdr"`
	ExtraFields            *[]string         `json:"extra_fields"`
	LowBalanceAnnFile      *string           `json:"low_balance_ann_file"`
	EmptyBalanceContext    *string           `json:"empty_balance_context"`
	EmptyBalanceAnnFile    *string           `json:"empty_balance_ann_file"`
	MaxWaitConnection      *string           `json:"max_wait_connection"`
	ActiveSessionDelimiter *string           `json:"active_session_delimiter"`
	EventSocketConns       *[]*FsConnJsonCfg `json:"event_socket_conns"`
	Request_processors     *[]*ReqProcessorJsnCfg
}

// Represents one connection instance towards FreeSWITCH
type FsConnJsonCfg struct {
	Address              *string `json:"address"`
	Password             *string `json:"password"`
	Reconnects           *int    `json:"reconnects"`
	MaxReconnectInterval *string `json:"max_reconnect_interval"`
	ReplyTimeout         *string `json:"reply_timeout"`
	Alias                *string `json:"alias"`
}

func diffFsConnJsonCfg(v1, v2 *FsConnCfg) (d *FsConnJsonCfg) {
	d = new(FsConnJsonCfg)
	if v1.Address != v2.Address {
		d.Address = utils.StringPointer(v2.Address)
	}
	if v1.Password != v2.Password {
		d.Password = utils.StringPointer(v2.Password)
	}
	if v1.Reconnects != v2.Reconnects {
		d.Reconnects = utils.IntPointer(v2.Reconnects)
	}
	if v1.MaxReconnectInterval != v2.MaxReconnectInterval {
		d.MaxReconnectInterval = utils.StringPointer(v2.MaxReconnectInterval.String())
	}
	if v1.ReplyTimeout != v2.ReplyTimeout {
		d.ReplyTimeout = utils.StringPointer(v2.ReplyTimeout.String())
	}
	if v1.Alias != v2.Alias {
		d.Alias = utils.StringPointer(v2.Alias)
	}
	return
}

func equalsFsConnsJsonCfg(v1, v2 []*FsConnCfg) bool {
	if len(v1) != len(v2) {
		return false
	}
	for i := range v2 {
		if v1[i].Address != v2[i].Address ||
			v1[i].Password != v2[i].Password ||
			v1[i].Reconnects != v2[i].Reconnects ||
			v1[i].MaxReconnectInterval != v2[i].MaxReconnectInterval ||
			v1[i].ReplyTimeout != v2[i].ReplyTimeout ||
			v1[i].Alias != v2[i].Alias {
			return false
		}
	}
	return true
}

func diffFreeswitchAgentJsonCfg(d *FreeswitchAgentJsonCfg, v1, v2 *FsAgentCfg) *FreeswitchAgentJsonCfg {
	if d == nil {
		d = new(FreeswitchAgentJsonCfg)
	}
	if v1.Enabled != v2.Enabled {
		d.Enabled = utils.BoolPointer(v2.Enabled)
	}
	if !slices.Equal(v1.SessionSConns, v2.SessionSConns) {
		d.SessionSConns = utils.SliceStringPointer(stripInternalConns(v2.SessionSConns))
	}
	if v1.SubscribePark != v2.SubscribePark {
		d.SubscribePark = utils.BoolPointer(v2.SubscribePark)
	}
	if v1.CreateCDR != v2.CreateCDR {
		d.CreateCDR = utils.BoolPointer(v2.CreateCDR)
	}
	extra1 := v1.ExtraFields.AsStringSlice()
	extra2 := v2.ExtraFields.AsStringSlice()
	if !slices.Equal(extra1, extra2) {
		d.ExtraFields = &extra2
	}
	if v1.LowBalanceAnnFile != v2.LowBalanceAnnFile {
		d.LowBalanceAnnFile = utils.StringPointer(v2.LowBalanceAnnFile)
	}
	if v1.EmptyBalanceContext != v2.EmptyBalanceContext {
		d.EmptyBalanceContext = utils.StringPointer(v2.EmptyBalanceContext)
	}
	if v1.EmptyBalanceAnnFile != v2.EmptyBalanceAnnFile {
		d.EmptyBalanceAnnFile = utils.StringPointer(v2.EmptyBalanceAnnFile)
	}
	if v1.MaxWaitConnection != v2.MaxWaitConnection {
		d.MaxWaitConnection = utils.StringPointer(v2.MaxWaitConnection.String())
	}
	if v1.ActiveSessionDelimiter != v2.ActiveSessionDelimiter {
		d.ActiveSessionDelimiter = utils.StringPointer(v2.ActiveSessionDelimiter)
	}

	if !equalsFsConnsJsonCfg(v1.EventSocketConns, v2.EventSocketConns) {
		v := make([]*FsConnJsonCfg, len(v2.EventSocketConns))
		dflt := getDftFsConnCfg()
		for i, val := range v2.EventSocketConns {
			v[i] = diffFsConnJsonCfg(dflt, val)
		}
		d.EventSocketConns = &v
	}
	return d
}
