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

import (
	"testing"
	"time"

	"github.com/cgrates/birpc/context"
	"github.com/cgrates/cgrates/config"
	"github.com/cgrates/cgrates/engine"
	"github.com/cgrates/cgrates/sessions"
	"github.com/cgrates/cgrates/utils"
	"github.com/cgrates/fsock"
)

func TestFAsSessionSClientIface(t *testing.T) {
	_ = sessions.BiRPCClient(new(FSsessions))
}

func TestFsAgentV1DisconnectPeer(t *testing.T) {
	ctx := context.Background()
	args := &utils.DPRArgs{}
	fss := &FSsessions{}
	err := fss.V1DisconnectPeer(ctx, args, nil)
	if err != utils.ErrNotImplemented {
		t.Errorf("Expected error: %v, got: %v", utils.ErrNotImplemented, err)
	}
}

func TestFsAgentV1AlterSession(t *testing.T) {
	ctx := context.Background()
	cgrEv := utils.CGREvent{}
	fss := &FSsessions{}
	err := fss.V1AlterSession(ctx, cgrEv, nil)
	if err != utils.ErrNotImplemented {
		t.Errorf("Expected error: %v, got: %v", utils.ErrNotImplemented, err)
	}
}

func TestFsAgentCreateHandlers(t *testing.T) {
	cfg := &config.FsAgentCfg{
		SubscribePark: true,
	}
	fs := &FSsessions{
		cfg: cfg,
	}
	handlers := fs.createHandlers()
	if _, ok := handlers["CHANNEL_ANSWER"]; !ok {
		t.Error("Expected CHANNEL_ANSWER handler, but not found")
	}
	if _, ok := handlers["CHANNEL_HANGUP_COMPLETE"]; !ok {
		t.Error("Expected CHANNEL_HANGUP_COMPLETE handler, but not found")
	}
	if _, ok := handlers["CHANNEL_PARK"]; !ok {
		t.Error("Expected CHANNEL_PARK handler, but not found")
	}
}

func TestFsAgentV1WarnDisconnect(t *testing.T) {

	cfg := &config.FsAgentCfg{
		LowBalanceAnnFile: "File",
		EventSocketConns: []*config.FsConnCfg{
			{
				Address:              "Address",
				Password:             "Password",
				Reconnects:           3,
				MaxReconnectInterval: 10 * time.Second,
				ReplyTimeout:         5 * time.Second,
				Alias:                "Alias",
			},
		},
	}

	fsa := &FSsessions{
		cfg:   cfg,
		conns: []*fsock.FSock{},
	}
	ctx := &context.Context{}
	args := map[string]interface{}{
		"OriginID": "ID",
		"FsConnID": int64(0),
	}
	var reply string
	fsa.cfg.LowBalanceAnnFile = utils.EmptyString
	err := fsa.V1WarnDisconnect(ctx, args, &reply)
	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}
	if reply != utils.OK {
		t.Errorf("Expected reply to be 'OK', got '%s'", reply)
	}
	fsa.cfg.LowBalanceAnnFile = "File"
	err = fsa.V1WarnDisconnect(ctx, args, &reply)
	if err == nil {
		t.Errorf("Index out of range[0,0): 0, got %v", err)
	}
	if reply != utils.OK {
		t.Errorf("Expected reply to be 'OK', got '%s'", reply)
	}
	delete(args, "FsConnID")
	err = fsa.V1WarnDisconnect(ctx, args, &reply)
	if err == nil {
		t.Error("Expected an error, got nil")
	}
}

func TestFsAgentReload(t *testing.T) {
	cfg := &config.FsAgentCfg{
		EventSocketConns: []*config.FsConnCfg{
			{
				Address:              "Address",
				Password:             "Password",
				Reconnects:           3,
				MaxReconnectInterval: 10 * time.Second,
				ReplyTimeout:         5 * time.Second,
				Alias:                "Alias",
			},
		},
	}
	fsa := &FSsessions{
		cfg: cfg,
	}
	fsa.Reload()

	if len(fsa.conns) != 1 {
		t.Errorf("Expected fsa.conns length to be 1, got %d", len(fsa.conns))
	}
	if len(fsa.senderPools) != 1 {
		t.Errorf("Expected fsa.senderPools length to be 1, got %d", len(fsa.senderPools))
	}

}

func TestFsAgentV1GetActiveSessionIDs(t *testing.T) {
	cfg := &config.FsAgentCfg{
		EventSocketConns: []*config.FsConnCfg{
			{
				Address:              "Address",
				Password:             "Password",
				Reconnects:           3,
				MaxReconnectInterval: 10 * time.Second,
				ReplyTimeout:         5 * time.Second,
				Alias:                "Alias",
			},
		},
		ActiveSessionDelimiter: ",",
	}

	fsa := &FSsessions{
		cfg: cfg,
	}

	ctx := context.Background()
	var sessionIDs []*sessions.SessionID
	err := fsa.V1GetActiveSessionIDs(ctx, "", &sessionIDs)
	if err == nil {
		t.Errorf("NO_ACTIVE_SESSION, got %v", err)
	}
	if len(sessionIDs) != 0 {
		t.Errorf("Expected sessionIDs slice to be populated, got empty")
	}

}

func TestFsAgentV1DisconnectSession(t *testing.T) {
	mockEvent := map[string]any{
		utils.OriginID:        "Id",
		utils.DisconnectCause: "Cause",
		FsConnID:              int64(0),
	}
	testReply := ""
	fsa := &FSsessions{
		conns: []*fsock.FSock{},
	}
	err := fsa.V1DisconnectSession(context.Background(), utils.CGREvent{Event: mockEvent}, &testReply)
	if err == nil {
		t.Errorf("Index out of range[0,0): 0, got %v", err)
	}
	if testReply == utils.OK {
		t.Errorf("Expected reply to be 'OK', got %s", testReply)
	}
}

func TestFsAgentShutdown(t *testing.T) {
	fsa := &FSsessions{
		conns: []*fsock.FSock{},
	}
	err := fsa.Shutdown()
	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}
}

func TestFsAgentNewFSsessions(t *testing.T) {
	fsAgentConfig := &config.FsAgentCfg{
		EventSocketConns: []*config.FsConnCfg{
			{
				Address:              "Address",
				Password:             "Password",
				Reconnects:           3,
				MaxReconnectInterval: time.Second * 30,
				ReplyTimeout:         time.Second * 10,
			},
		},
	}
	timezone := "UTC"
	connMgr := &engine.ConnManager{}

	fsa, err := NewFSsessions(fsAgentConfig, timezone, connMgr)
	if err != nil {
		t.Fatalf("Expected no error, got %v", err)
	}
	if fsa == nil {
		t.Fatal("Expected non-nil FSsessions instance, got nil")
	}
	if fsa.cfg != fsAgentConfig {
		t.Errorf("Expected fsa.cfg to be %v, got %v", fsAgentConfig, fsa.cfg)
	}
	if len(fsa.conns) != len(fsAgentConfig.EventSocketConns) {
		t.Errorf("Expected fsa.conns length to be %d, got %d", len(fsAgentConfig.EventSocketConns), len(fsa.conns))
	}
	if len(fsa.senderPools) != len(fsAgentConfig.EventSocketConns) {
		t.Errorf("Expected fsa.senderPools length to be %d, got %d", len(fsAgentConfig.EventSocketConns), len(fsa.senderPools))
	}
	if fsa.timezone != timezone {
		t.Errorf("Expected fsa.timezone to be %s, got %s", timezone, fsa.timezone)
	}
	if fsa.connMgr != connMgr {
		t.Errorf("Expected fsa.connMgr to be %v, got %v", connMgr, fsa.connMgr)
	}
	if fsa.ctx == nil {
		t.Error("Expected fsa.ctx to be initialized, got nil")
	}
}

func TestFSsessionsV1DisconnectPeer(t *testing.T) {
	fsSessions := &FSsessions{

		connMgr: &engine.ConnManager{},
	}
	ctx := context.Background()
	args := &utils.DPRArgs{}
	reply := ""
	err := fsSessions.V1DisconnectPeer(ctx, args, &reply)
	if err != utils.ErrNotImplemented {
		t.Errorf("Expected error %v, got %v", utils.ErrNotImplemented, err)
	}
}

func TestFSsessionsV1AlterSession(t *testing.T) {
	fsSessions := &FSsessions{

		connMgr: &engine.ConnManager{},
	}
	ctx := context.Background()
	event := utils.CGREvent{}
	reply := ""
	err := fsSessions.V1AlterSession(ctx, event, &reply)
	if err != utils.ErrNotImplemented {
		t.Errorf("Expected error %v, got %v", utils.ErrNotImplemented, err)
	}
}

func TestNewFSsessions(t *testing.T) {
	fsAgentConfig := &config.FsAgentCfg{}
	timezone := "UTC"
	connMgr := &engine.ConnManager{}
	fsSessions, err := NewFSsessions(fsAgentConfig, timezone, connMgr)
	if err != nil {
		t.Fatalf("Expected no error, got %v", err)
	}
	if fsSessions == nil {
		t.Fatalf("Expected fsSessions to be non-nil")
	}
	if fsSessions.cfg != fsAgentConfig {
		t.Errorf("Expected cfg to be %v, got %v", fsAgentConfig, fsSessions.cfg)
	}
	if len(fsSessions.conns) != len(fsAgentConfig.EventSocketConns) {
		t.Errorf("Expected conns length %d, got %d", len(fsAgentConfig.EventSocketConns), len(fsSessions.conns))
	}
	if len(fsSessions.senderPools) != len(fsAgentConfig.EventSocketConns) {
		t.Errorf("Expected senderPools length %d, got %d", len(fsAgentConfig.EventSocketConns), len(fsSessions.senderPools))
	}
	if fsSessions.timezone != timezone {
		t.Errorf("Expected timezone to be %s, got %s", timezone, fsSessions.timezone)
	}
	if fsSessions.connMgr != connMgr {
		t.Errorf("Expected connMgr to be %v, got %v", connMgr, fsSessions.connMgr)
	}
	if fsSessions.ctx == nil {
		t.Error("Expected ctx to be non-nil")
	}
}

func TestFSsessionsCreateHandlers(t *testing.T) {
	tests := []struct {
		name             string
		subscribePark    bool
		expectedHandlers []string
	}{
		{
			name:             "Without SubscribePark",
			subscribePark:    false,
			expectedHandlers: []string{"CHANNEL_ANSWER", "CHANNEL_HANGUP_COMPLETE"},
		},
		{
			name:             "With SubscribePark",
			subscribePark:    true,
			expectedHandlers: []string{"CHANNEL_ANSWER", "CHANNEL_HANGUP_COMPLETE", "CHANNEL_PARK"},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {

			fsSessions := &FSsessions{
				cfg: &config.FsAgentCfg{
					SubscribePark: tt.subscribePark,
				},
			}
			handlers := fsSessions.createHandlers()
			for _, expectedHandler := range tt.expectedHandlers {
				if _, exists := handlers[expectedHandler]; !exists {
					t.Errorf("Expected handler %s to be present, but it was not", expectedHandler)
				}
			}
			for handler := range handlers {
				found := false
				for _, expectedHandler := range tt.expectedHandlers {
					if handler == expectedHandler {
						found = true
						break
					}
				}
				if !found {
					t.Errorf("Unexpected handler %s found", handler)
				}
			}
		})
	}
}
func TestFSsessionsReload(t *testing.T) {
	eventSocketConns := []string{"conn1", "conn2", "conn3"}
	cfg := &config.FsAgentCfg{}

	fsa := &FSsessions{
		cfg:         cfg,
		conns:       make([]*fsock.FSock, 1),
		senderPools: make([]*fsock.FSockPool, 1),
	}

	fsa.Reload()

	if len(fsa.conns) == len(eventSocketConns) {
		t.Errorf("Expected conns length %d, got %d", len(eventSocketConns), len(fsa.conns))
	}

	if len(fsa.senderPools) == len(eventSocketConns) {
		t.Errorf("Expected senderPools length %d, got %d", len(eventSocketConns), len(fsa.senderPools))
	}

	for i, conn := range fsa.conns {
		if conn != nil {
			t.Errorf("Expected conns[%d] to be nil, got %v", i, conn)
		}
	}
	for i, pool := range fsa.senderPools {
		if pool != nil {
			t.Errorf("Expected senderPools[%d] to be nil, got %v", i, pool)
		}
	}
}

func TestFSsessionsV1WarnDisconnect(t *testing.T) {
	cfg := &config.FsAgentCfg{}
	fsa := FSsessions{
		cfg: cfg,
	}
	ctx := context.Background()
	args := map[string]any{}
	var reply string
	err := fsa.V1WarnDisconnect(ctx, args, &reply)
	if err != nil {
		t.Errorf("Expected no error, but got: %v", err)
	}
	if reply != utils.OK {
		t.Errorf("Expected reply: %s, but got: %s", utils.OK, reply)
	}

}
