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

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"jobarranger2/src/libs/golibs/common"
	"jobarranger2/src/libs/golibs/config_reader/server"
	"jobarranger2/src/libs/golibs/logger/logger"
	wm "jobarranger2/src/libs/golibs/worker_manager"

	"golang.org/x/sys/unix"
)

// entry function to start re-creating events for delayed transaction files
func recoverDelayedTransacFiles() {
	fn := "recoverDelayedTransacFiles"
	logData := logger.Logging{}
	processTimeouts := map[string]int{
		common.JobnetManagerFolder:       server.Options.JobnetManagerTransactionTimeout,
		common.DBSyncerManagerFolder:     server.Options.DBSyncerManagerTransactionTimeout,
		common.FlowManagerFolder:         server.Options.FlowManagerTransactionTimeout,
		common.IconExecManagerFolder:     server.Options.IconExecManagerTransactionTimeout,
		common.IconResultManagerFolder:   server.Options.IconResultManagerTransactionTimeout,
		common.NotificationManagerFolder: server.Options.NotificationManagerTransactionTimeout,
	}

	logger.JaLog("JARECOVERYDELAY000001", logData, fn)

	var wg sync.WaitGroup

	for {
		select {
		case <-wm.Wm.Ctx.Done():
			logger.JaLog("JARECOVERYDELAY400007", logData, fn, RecoverDelayedTransacFilesWorkerID)
			return
		case <-time.After(1 * time.Second):
			wm.Wm.MonitorChan <- RecoverDelayedTransacFilesWorkerID
		}

		for name, timeout := range processTimeouts {
			wg.Add(1)

			go func(folderName string, timeoutSec int64) {
				defer wg.Done()
				logger.JaLog("JARECOVERYDELAY400001", logData, fn, folderName, timeoutSec)
				if err := handleDelayedFiles(folderName, timeoutSec, "in"); err != nil {
					logger.JaLog("JARECOVERYDELAY200001", logData, fn, folderName, err.Error())
				}
				logger.JaLog("JARECOVERYDELAY400002", logData, fn, folderName, timeoutSec)
			}(name, int64(timeout))
		}

		wg.Wait()

		time.Sleep(1 * time.Second)
	}
}

func handleDelayedFiles(folderName string, timeoutSeconds int64, subFolder string) error {
	fn := "handleDelayedFiles"
	logData := logger.Logging{}
	targetDir := filepath.Join(server.Options.TmpDir, folderName, subFolder)

	if _, err := os.Stat(targetDir); err != nil {
		// Warning log here
		logger.JaLog("JARECOVERYDELAY300001", logData, fn, targetDir)
		return nil
	}

	if err := revertTmpFiles(targetDir); err != nil {
		return err
	}
	logger.JaLog("JARECOVERYDELAY400003", logData, fn)

	var jsonFiles []string

	// Scan the directory for .json files
	logger.JaLog("JARECOVERYDELAY400004", logData, fn, targetDir)
	err := filepath.WalkDir(targetDir, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if !d.IsDir() && strings.HasSuffix(d.Name(), ".json") {
			jsonFiles = append(jsonFiles, d.Name())
		}
		return nil
	})
	if err != nil {
		return fmt.Errorf("walk failed: %w", err)
	}

	for _, file := range jsonFiles {
		fullPath := filepath.Join(targetDir, file)

		dateStr, err := extractFileTimestamp(fullPath)
		if err != nil {
			if errors.Is(err, fs.ErrNotExist) || errors.Is(err, unix.ENOENT) {
				logger.JaLog("JARECOVERYDELAY000002", logData, fn, file, err.Error())
			} else {
				logger.JaLog("JARECOVERYDELAY200002", logData, fn, file, err.Error())
			}
			continue
		}

		fileTime, err := time.ParseInLocation("20060102150405", dateStr, time.Local)
		if err != nil {
			logger.JaLog("JARECOVERYDELAY200003", logData, fn, file, err.Error())
			continue
		}

		if time.Since(fileTime).Seconds() > float64(timeoutSeconds) {
			tmpPath := strings.Replace(fullPath, ".json", ".json.tmp", 1)
			if err := os.Rename(fullPath, tmpPath); err != nil {
				if errors.Is(err, fs.ErrNotExist) {
					logger.JaLog("JARECOVERYDELAY000003", logData, fn, fullPath, tmpPath, err)
				} else {
					logger.JaLog("JARECOVERYDELAY200004", logData, fn, fullPath, tmpPath, err)
				}
				continue
			}
			logger.JaLog("JARECOVERYDELAY400005", logData, fn, fullPath, tmpPath)

			if err := os.Rename(tmpPath, fullPath); err != nil {
				if errors.Is(err, fs.ErrNotExist) {
					logger.JaLog("JARECOVERYDELAY000003", logData, fn, tmpPath, fullPath, err)
				} else {
					logger.JaLog("JARECOVERYDELAY200004", logData, fn, tmpPath, fullPath, err)
				}
				continue
			}
			logger.JaLog("JARECOVERYDELAY400006", logData, fn, tmpPath, fullPath)
		}
	}

	return nil
}

func extractFileTimestamp(filePath string) (string, error) {
	var stat unix.Stat_t
	if err := unix.Stat(filePath, &stat); err != nil {
		return "", fmt.Errorf("unix stat failed: %w", err)
	}
	return time.Unix(stat.Ctim.Sec, stat.Ctim.Nsec).Format("20060102150405"), nil
}

// Revert the .json.tmp files that are left due to incomplete process
func revertTmpFiles(dir string) error {
	fn := "revertTmpFiles"
	const revertAfter = 5 * time.Second

	return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			if errors.Is(err, fs.ErrNotExist) {
				logger.JaLog("JARECOVERYDELAY000004", logger.Logging{}, fn, path, err)
				return nil
			}
			return err
		}

		if d.IsDir() || !strings.HasSuffix(d.Name(), ".json.tmp") {
			return nil
		}

		info, err := d.Info()
		if err != nil {
			if errors.Is(err, fs.ErrNotExist) {
				logger.JaLog("JARECOVERYDELAY000004", logger.Logging{}, fn, path, err)
				return nil
			}
			return err
		}

		// Check modified time
		if time.Since(info.ModTime()) <= revertAfter {
			return nil
		}

		originalPath := strings.TrimSuffix(path, ".tmp")
		if err := os.Rename(path, originalPath); err != nil {
			if errors.Is(err, fs.ErrNotExist) {
				logger.JaLog("JARECOVERYDELAY000004", logger.Logging{}, fn, path, err)
				return nil
			}
			return fmt.Errorf("failed to revert previous temp file %s: %w", path, err)
		}
		logger.JaLog("JARECOVERYDELAY400008", logger.Logging{}, fn, path, originalPath)

		return nil
	})
}
