//go:build linux
// +build linux

/*
** 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 (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"runtime/debug"
	"strconv"
	"strings"
	"syscall"
	"time"

	"jobarranger2/src/libs/golibs/common"
	"jobarranger2/src/libs/golibs/logger/logger"
	wm "jobarranger2/src/libs/golibs/worker_manager"
)

func sendHeartbeatToCmonitor(cMonitorPid int) {
	syscall.Kill(cMonitorPid, syscall.Signal(SIGRTMIN1))
}

func exitProcess() {
	syscall.Kill(os.Getpid(), syscall.SIGHUP)
}

func monitorPPID() {
	workerId := "monitorPPID"

	for {
		select {
		case <-wm.Wm.Ctx.Done():
			logger.JaLog("JAFRAMEWORK400004", logger.Logging{}, common.Manager.Name, workerId)
			return
		case <-time.After(1 * time.Second):
			wm.Wm.MonitorChan <- workerId
		}

		if os.Getppid() == 1 {
			logger.JaLog("JAFRAMEWORK200009", logger.Logging{}, common.Manager.Name)
			syscall.Kill(os.Getpid(), syscall.SIGTERM)
		}

		time.Sleep(60 * time.Second)
	}

}

func isZombieAndMyChild(pid int, myPid int) bool {
	statusPath := fmt.Sprintf("/proc/%d/status", pid)
	data, err := os.ReadFile(statusPath)
	if err != nil {
		return false
	}

	var state, ppid string

	for _, line := range strings.Split(string(data), "\n") {
		if strings.HasPrefix(line, "State:") {
			state = line
		} else if strings.HasPrefix(line, "PPid:") {
			ppid = line
		}
	}

	if !strings.Contains(state, "Z") {
		return false
	}

	ppidFields := strings.Fields(ppid)
	if len(ppidFields) < 2 {
		return false
	}

	parentPid, err := strconv.Atoi(ppidFields[1])
	if err != nil {
		return false
	}

	return parentPid == myPid
}

func collectZombieChildren() {
	workerId := "collectZombieChildren"

	for {
		select {
		case <-wm.Wm.Ctx.Done():
			logger.JaLog("JAFRAMEWORK400004", logger.Logging{}, common.Manager.Name, workerId)
			return
		case <-time.After(1 * time.Second):
			wm.Wm.MonitorChan <- workerId
		}

		myPid := os.Getpid()

		entries, err := os.ReadDir("/proc")
		if err != nil {
			logger.JaLog("JAFRAMEWORK200008", logger.Logging{}, common.Manager.Name, workerId, err)
			return
		}

		for _, entry := range entries {
			if !entry.IsDir() {
				continue
			}
			pid, err := strconv.Atoi(entry.Name())
			if err != nil {
				continue
			}
			if !isZombieAndMyChild(pid, myPid) {
				continue
			}

			var status syscall.WaitStatus
			var rusage syscall.Rusage
			_, err = syscall.Wait4(pid, &status, syscall.WNOHANG, &rusage)
			if err != nil {
				logger.JaLog("JAFRAMEWORK200008", logger.Logging{}, common.Manager.Name, workerId, err)
			}
		}

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

const (
	// Real-time signals
	SIGRTMIN  = 34
	SIGRTMIN1 = 35
	SIGRTMIN2 = 36
	SIGRTMIN3 = 37
	SIGRTMIN4 = 38
	SIGRTMAX  = 64
)

func waitStop(cMonitorPid int) {
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan)
	signal.Ignore(syscall.SIGURG, syscall.SIGCHLD)

	for sig := range sigChan {
		switch sig {
		case syscall.SIGHUP, syscall.SIGTERM:
			logger.JaLog("JAFRAMEWORK000003", logger.Logging{}, common.Manager.Name)
			//Send SIGRTMIN to c monior
			syscall.Kill(cMonitorPid, syscall.Signal(SIGRTMIN))
			//Stop all running routines
			wm.ProcessExitFlag = true
			doneChan := make(chan bool)
			wm.StopAllWorkers(doneChan)

			select {
			case <-doneChan:
				logger.JaLog("JAFRAMEWORK000004", logger.Logging{}, common.Manager.Name)
			case <-time.After(10 * time.Second):
				logger.JaLog("JAFRAMEWORK300001", logger.Logging{}, common.Manager.Name)
				syscall.Kill(os.Getpid(), syscall.SIGKILL)
			}

			if sig == syscall.SIGHUP {
				os.Exit(129)
			}
			os.Exit(0)

		case syscall.SIGILL, syscall.SIGBUS, syscall.SIGFPE, syscall.SIGSEGV, syscall.SIGUSR1, syscall.SIGUSR2:
			//Output stacktrace
			logger.WriteLog("JAFRAMEWORK100001", common.Manager.Name, debug.Stack())
			//Send SIGRTMIN to c monior
			syscall.Kill(cMonitorPid, syscall.Signal(SIGRTMIN))
			//Stop all running routines
			wm.ProcessExitFlag = true
			doneChan := make(chan bool)
			wm.StopAllWorkers(doneChan)

			select {
			case <-doneChan:
				logger.JaLog("JAFRAMEWORK000004", logger.Logging{}, common.Manager.Name)
			case <-time.After(1 * time.Minute):
				logger.JaLog("JAFRAMEWORK300001", logger.Logging{}, common.Manager.Name)
				syscall.Kill(os.Getpid(), syscall.SIGKILL)
			}
			os.Exit(1)

		default:
			sigNum := -1
			if s, ok := sig.(syscall.Signal); ok {
				sigNum = int(s)
			}
			logger.JaLog("JAFRAMEWORK000005", logger.Logging{}, common.Manager.Name, sig, sigNum)
		}
	}
}

type args struct {
	cMonitorPid  int
	goPluginId   int
	configPath   string
	targetServer string
	useDB        bool
	useTCP       bool
	help         bool
	version      bool
}

func showUsage(fs *flag.FlagSet) {
	fmt.Fprintf(fs.Output(), "Usage of %s:\n", os.Args[0])
	fs.PrintDefaults()
}

func helpMessage() {
	fmt.Println("This is a help message")
}

func versionInfo() {
	fmt.Println("Job Arranger Agent (daemon) v7.2.0 (revision 1234) (2025-04-29)")
}

func parseArgs() (args, *flag.FlagSet) {
	var args args
	flagSet := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)

	flagSet.IntVar(&args.cMonitorPid, "c-monitor-pid", 0, "PID of C monitoring process (Mandatory)")
	flagSet.IntVar(&args.goPluginId, "go-plugin-id", -1, "Go plugin type (Mandatory)")
	flagSet.BoolVar(&args.useDB, "use-db", false, "Enable database usage")
	flagSet.BoolVar(&args.useTCP, "use-tcp", false, "Enable TCP communication")
	flagSet.StringVar(&args.configPath, "config-path", "", "Path to the configuration file (Mandatory)")
	flagSet.StringVar(&args.targetServer, "target-server", "", "Flag to define JAZ server or agent")
	flagSet.BoolVar(&args.help, "help", false, "Usage message")
	flagSet.BoolVar(&args.version, "version", false, "framework version")

	flagSet.Usage = func() {
		showUsage(flagSet)
	}
	flagSet.SetOutput(os.Stderr)

	if err := flagSet.Parse(os.Args[1:]); err != nil {
		log.Fatalf("Error: %v", err)
	}

	logger.JaLog(
		"JAFRAMEWORK400001",
		logger.Logging{},
		args.cMonitorPid,
		args.goPluginId,
		args.useDB,
		args.useTCP,
		args.configPath,
		args.targetServer,
	)

	return args, flagSet
}

func checkArgs(args args) {
	var missingFlags []string

	if args.cMonitorPid == 0 {
		missingFlags = append(missingFlags, "-c-monitor-pid")
	}
	if args.goPluginId == -1 {
		missingFlags = append(missingFlags, "-go-plugin-id")
	}
	if args.configPath == "" {
		missingFlags = append(missingFlags, "-config-path")
	}

	if len(missingFlags) > 0 {
		log.Printf("Missing required flags: %v", missingFlags)
		os.Exit(1)
	}

	if args.targetServer != "server" && args.targetServer != "agent" {
		log.Printf("Invalid target-server value: %s (must be 'server' or 'agent')", args.targetServer)
		os.Exit(1)
	}

}

func runAgentManagerProcess(data common.Data, agentManagerExecPath string, eventRoute int) (*exec.Cmd, error) {
	return nil, nil
}

func sendSignal() {
	syscall.Kill(os.Getppid(), syscall.Signal(SIGRTMAX))
}
