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

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"jobarranger2/src/libs/golibs/common"
)

// CreateJsonFile creates a JSON file from the given event data.
// It generates a temporary `.tmp` file using the jobnet ID and job ID for naming,
// writes the JSON data into it, then renames the file to have a `.json` extension.
//
// The file paths are determined based on the event type using the transactionFolders map.
// The final file metadata (source and destination paths) is appended to data.Transfer.Files.
//
// Example usage:
//
//	err := common.CreateJsonFile(data, 1234, 5678)
//	if err != nil {
//	    log.Fatal(err)
//	}
func CreateJsonFile(data *common.EventData, innerJobnetId uint64, jobnetId string, innerJobId uint64) error {
	var jsonFile *os.File
	var source_folder, dest_folder string
	var fileSource common.FileTransfer
	var err error

	// funcName := "CreateJsonFile"

	folderInfo, ok := common.TransactionFolders[data.Event.Name]
	if !ok {
		return fmt.Errorf("could not find designated transaction folder for event[%s]", data.Event.Name)
	}
	source_folder = folderInfo.Source
	dest_folder = folderInfo.Destination

	// Ensure destination directory exists
	parentFolder := filepath.Join(common.ServerTmpFolderPath, source_folder)
	if err := os.MkdirAll(parentFolder, common.FilePermission); err != nil {
		return fmt.Errorf("failed to create target directory %s, err: [%w]", parentFolder, err)
	}

	timeFormat := time.Now().Format("20060102150405.000000000")
	timeStamp := strings.ReplaceAll(timeFormat, ".", "_")
	tmpFilename := fmt.Sprintf("%d_%s_%d_%s.tmp", innerJobnetId, jobnetId, innerJobId, timeStamp)
	tmpFileSource := filepath.Join(common.ServerTmpFolderPath, source_folder, tmpFilename)

	if jsonFile, err = os.Create(tmpFileSource); err != nil {
		return fmt.Errorf("create json:%s file failed under %s. [%v] ", tmpFilename, source_folder, err)
	}
	defer jsonFile.Close()

	jsonFileName := strings.TrimSuffix(tmpFilename, ".tmp") + ".json"
	fileSource.Source = filepath.Join(common.ServerTmpFolderPath, source_folder, jsonFileName)
	if strings.TrimSpace(dest_folder) != "" {
		fileSource.Destination = filepath.Join(common.ServerTmpFolderPath, dest_folder, jsonFileName)
	}

	data.Transfer.Files = append(data.Transfer.Files, fileSource)
	data.Transfer.UDS.SocketPath = common.GetNextCommunicationSocketPath(data.Event.Name)

	if err = WriteJSON(jsonFile, *data); err != nil {
		return fmt.Errorf("write json:%v file failed. [%v] ", (*jsonFile).Name(), err)
	}
	if err = os.Rename(tmpFileSource, fileSource.Source); err != nil {
		return fmt.Errorf("failed to rename file from %s to %s. [%v]", tmpFileSource, fileSource.Source, err)
	}

	return err
}

func CreateJsonFileAt(data *common.EventData, innerJobnetId uint64, jobnetId string, innerJobId uint64, srcFolder string) error {
	var jsonFile *os.File
	var source_folder, dest_folder string
	var fileSource common.FileTransfer
	var err error

	// funcName := "CreateJsonFileAt"

	folderInfo, ok := common.TransactionFolders[data.Event.Name]
	if !ok {
		return fmt.Errorf("could not find designated transaction folder for event[%s]", data.Event.Name)
	}
	source_folder = folderInfo.Source
	dest_folder = folderInfo.Destination

	// Ensure destination directory exists
	parentFolder := filepath.Join(common.ServerTmpFolderPath, srcFolder)
	if err := os.MkdirAll(parentFolder, common.FilePermission); err != nil {
		return fmt.Errorf("failed to create target directory %s, err: [%w]", parentFolder, err)
	}

	timeFormat := time.Now().Format("20060102150405.000000000")
	timeStamp := strings.ReplaceAll(timeFormat, ".", "_")
	tmpFilename := fmt.Sprintf("%d_%s_%d_%s.json", innerJobnetId, jobnetId, innerJobId, timeStamp)
	tmpFileSource := filepath.Join(common.ServerTmpFolderPath, srcFolder, tmpFilename)

	if jsonFile, err = os.Create(tmpFileSource); err != nil {
		return fmt.Errorf("create json:%s file failed under %s. [%v] ", tmpFilename, source_folder, err)
	}
	defer jsonFile.Close()

	jsonFileName := tmpFilename
	fileSource.Source = filepath.Join(common.ServerTmpFolderPath, source_folder, jsonFileName)
	if strings.TrimSpace(dest_folder) != "" {
		fileSource.Destination = filepath.Join(common.ServerTmpFolderPath, dest_folder, jsonFileName)
	}

	data.Transfer.Files = append(data.Transfer.Files, fileSource)
	data.Transfer.UDS.SocketPath = common.GetNextCommunicationSocketPath(data.Event.Name)

	if err = WriteJSON(jsonFile, *data); err != nil {
		return fmt.Errorf("write json:%v file failed. [%v] ", (*jsonFile).Name(), err)
	}

	return err
}

// WriteJSON writes the entire EventData struct to the file in formatted JSON.
//
// Usage:
//
//	err := common.WriteJSONField(jsonFile, "event", eventStruct, true)
//	if err != nil {
//	    log.Fatal(err)
//	}
func WriteJSON(jsonFile *os.File, data common.EventData) error {
	jsonData, err := json.MarshalIndent(data, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal full JSON struct: %v", err)
	}

	_, err = jsonFile.Write(jsonData)
	if err != nil {
		return fmt.Errorf("failed to write full JSON to file: %v", err)
	}

	// Optionally write a newline at the end
	_, _ = jsonFile.Write([]byte("\n"))
	return nil
}

// WriteJSONFile writes the entire EventData struct to the file in formatted JSON.
func WriteJSONFile(filePath string, data common.EventData) error {
	jsonFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, common.FilePermission)
	if err != nil {
		return fmt.Errorf("failed to open file '%s': %v", filePath, err)
	}
	defer jsonFile.Close()

	return WriteJSON(jsonFile, data)
}

// StartJSONFile writes the opening curly brace '{\n' to the file,
// indicating the start of a JSON object.
//
// Usage:
//
//	err := common.StartJSONFile(jsonFile)
//	if err != nil {
//	    log.Fatal(err)
//	}
func StartJSONFile(jsonFile *os.File) error {

	_, err := jsonFile.WriteString("{\n")
	return err
}

// WriteJSONField writes a single key-value pair to the file in JSON format.
// If addComma is true, it appends a comma after the field.//
// Parameters:
//   - jsonFile: a pointer to an *os.File where the JSON content will be written.
//   - key: the field name to use as the JSON key.
//   - value: the value to be marshaled into JSON and associated with the key.
//   - addComma: if true, a comma is added at the end of the line (useful when writing multiple fields in a JSON object).
//
// Example usage:
//
//	file, _ := os.Create("output.json")
//	defer file.Close()
//
//	fmt.Fprintln(file, "{") // start of JSON object
//	WriteJSONField(file, "name", "Alice", true)
//	WriteJSONField(file, "age", 30, false)
//	fmt.Fprintln(file, "}") // end of JSON object
//
// This function is useful for programmatically generating a JSON object field-by-field when full JSON marshaling is not desirable.
func WriteJSONField(jsonFile *os.File, key string, value any, addComma bool) error {

	data, err := json.MarshalIndent(value, "  ", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal %s: %v", key, err)
	}

	line := fmt.Sprintf(`  "%s": %s`, key, string(data))
	if addComma {
		line += ","
	}
	line += "\n"

	_, err = jsonFile.WriteString(line)
	if err != nil {
	}
	return err
}

// EndJSONFile writes the closing curly brace '}\n' to the file,
// indicating the end of a JSON object
//
// Usage:
//
//	err := common.EndJSONFile(jsonFile)
//	if err != nil {
//	    log.Fatal(err)
//	}
func EndJSONFile(jsonFile *os.File) error {

	_, err := jsonFile.WriteString("}\n")
	return err
}
