/*
** Job Arranger for ZABBIX
** Copyright (C) 2025 Daiwa Institute of Research Ltd. All Rights Reserved.
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 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 General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**/

package agentutils

import (
	"bufio"
	"encoding/binary"
	"fmt"
	"io"
	"net"
	"strings"

	"jobarranger2/src/libs/golibs/common"
	"jobarranger2/src/libs/golibs/config_reader/agent"
	jatcp "jobarranger2/src/libs/golibs/ja_tcp"
	"jobarranger2/src/libs/golibs/utils"
)

func ConnectServer(serverID, serverIPsFilePath string) (*common.NetConnection, error) {
	serverIP, err := GetIPByServerID(serverIPsFilePath, serverID)
	if err != nil {
		return nil, fmt.Errorf("failed to get server IP: %v", err)
	}

	tcpClient := jatcp.CreateTcpClient(serverIP, agent.Options.JaServerPort, uint(agent.Options.Timeout), "")
	conn, err := tcpClient.Connect()
	if err != nil {
		return nil, fmt.Errorf("failed to connect server [%s:%d]: %v", serverIP, agent.Options.JaServerPort, err)
	}

	return conn, nil
}

func SendToServer(eventData *common.EventData, netConn *common.NetConnection) error {
	if *eventData.TCPMessage.JazVersion == 1 {
		// jaz 1
		if err := netConn.Send(eventData.TCPMessage); err != nil {
			return fmt.Errorf("error sending via tcp [data=%s] to server: %w", utils.ToReadableString(eventData.TCPMessage), err)
		}
	} else {
		// jaz 2
		if err := netConn.Send(eventData); err != nil {
			return fmt.Errorf("error sending via tcp [data=%s] to server: %w", utils.ToReadableString(eventData), err)
		}
	}

	return nil
}

func IsInAllowedList(host string, allowedValuesStr string) bool {
	for allowed := range strings.SplitSeq(allowedValuesStr, ",") {
		if strings.TrimSpace(allowed) == host {
			return true
		}
	}
	return false
}

func GetConnectionIP(conn *common.NetConnection) (string, error) {
	remoteAddr := conn.RemoteAddr()
	if remoteAddr == nil {
		return "", fmt.Errorf("conn is nil")
	}
	host, _, err := net.SplitHostPort(remoteAddr.String())
	if err != nil {
		return "", fmt.Errorf("failed to split remoteAddr: %w", err)
	}
	return host, nil
}

func SendFileCount(conn *common.NetConnection, count int32) error {
	if err := binary.Write(conn, binary.LittleEndian, count); err != nil {
		return fmt.Errorf("file count send error: %v", err)
	}
	return nil
}

// SendString writes a length-prefixed string to the writer.
func SendString(w *common.NetConnection, s string) error {
	length := int32(len(s))
	if err := binary.Write(w, binary.LittleEndian, length); err != nil {
		return fmt.Errorf("failed to write string length: %w", err)
	}
	if _, err := w.Write([]byte(s)); err != nil {
		return fmt.Errorf("failed to write string content: %w", err)
	}
	return nil
}

func ReceiveString(conn *common.NetConnection) (string, error) {
	reader := bufio.NewReader(conn)

	// Peek first 5 bytes without consuming
	peeked, err := reader.Peek(5)
	if err != nil && err != io.EOF {
		return "", fmt.Errorf("failed to peek header: %w", err)
	}

	if len(peeked) >= 4 && string(peeked[:4]) == "ZBXD" {
		// It's Zabbix protocol
		_, _ = reader.Discard(5) // discard ZBXD + version

		// Read 8-byte payload length (little endian uint64)
		var payloadLen uint64
		if err := binary.Read(reader, binary.LittleEndian, &payloadLen); err != nil {
			return "", fmt.Errorf("failed to read payload length: %w", err)
		}

		// Read the actual payload
		payload := make([]byte, payloadLen)
		if _, err := io.ReadFull(reader, payload); err != nil {
			return "", fmt.Errorf("failed to read payload: %w", err)
		}
		return strings.TrimSpace(string(payload)), nil
	}

	// Not a Zabbix message
	var size int32
	if err := binary.Read(reader, binary.LittleEndian, &size); err == nil {
		buf := make([]byte, size)
		if _, err := io.ReadFull(reader, buf); err != nil {
			return "", fmt.Errorf("failed to read length-prefixed message: %w", err)
		}
		return strings.TrimSpace(string(buf)), nil
	}

	return "", nil

}
