//go:build windows
// +build windows

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

/*
#cgo LDFLAGS: -ldbghelp
#include <windows.h>
#include <dbghelp.h>
#include <stdlib.h>
*/
import "C"
import (
	"encoding/gob"
	"encoding/json"
	"flag"
	"log"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"syscall"
	"time"
	"unsafe"

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

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

var (
	modkernel32 = syscall.NewLazyDLL("kernel32.dll")
	moddbghelp  = syscall.NewLazyDLL("dbghelp.dll")
	ws2_32      = windows.NewLazyDLL("ws2_32.dll")

	wsaDuplicateSocket              = ws2_32.NewProc("WSADuplicateSocketW")
	procSetUnhandledExceptionFilter = modkernel32.NewProc("SetUnhandledExceptionFilter")
	procMiniDumpWriteDump           = moddbghelp.NewProc("MiniDumpWriteDump")
)

const (
	MiniDumpWithFullMemory = 0x00000002
)

func sendHeartbeatToCmonitor(cMonitorPid int) {}

func exitProcess() {
	sendServiceStop()
}

func monitorPPID() {}

func collectZombieChildren() {}

func processSignalHandler(cMonitorPid int) {}

type args struct {
	cMonitorPid  int
	goPluginId   int
	configPath   string
	targetServer string
	useDB        bool
	useTCP       bool
	help         bool
	version      bool
	foreground   bool
	install      bool
	unInstall    bool
	start        bool
	stop         bool
	startupType  string
}

// func showUsage(fs *flag.FlagSet) {
// 	fmt.Fprintf(fs.Output(), "Usage of %s:\n", os.Args[0])
// 	sigs := make(chan os.Signal, 1)
// 	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
// 	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)

	// Define flags
	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.StringVar(&args.configPath, "config-path", "", "Path to the Argsuration file (Mandatory)")
	flagSet.StringVar(&args.targetServer, "target-server", "", "Flag to define JAZ server or agent")
	flagSet.BoolVar(&args.useDB, "use-db", false, "Enable database usage")
	flagSet.BoolVar(&args.useTCP, "use-tcp", false, "Enable TCP communication")
	flagSet.BoolVar(&args.help, "help", false, "Usage message")
	flagSet.BoolVar(&args.version, "version", false, "framework version")
	flagSet.BoolVar(&args.foreground, "foreground", false, "Running foreground")
	flagSet.BoolVar(&args.install, "install", false, "install Job Arranger Agent as service")
	flagSet.BoolVar(&args.unInstall, "uninstall", false, "Uninstall Job Arranger Agent from service")
	flagSet.BoolVar(&args.start, "start", false, "start Job Arranger Agent service")
	flagSet.BoolVar(&args.stop, "stop", false, "stop Job Arranger Agent service")
	flagSet.StringVar(&args.startupType, "startup-type", "", "Set startup type of the Job Arranger Agent service to be installed.")

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

	if extras := flagSet.Args(); len(extras) > 0 {
		log.Printf("Warning: unknown arguments: %v", extras)
		os.Exit(1)
	}

	return args, flagSet
}

func checkArgs(args args) {
	// Open event log (only for windows)
	err := openEventLog()
	if err != nil {
		log.Fatalf("failed to open event log: %s", err.Error())
	}

	svcInstallFlag = args.install
	svcUninstallFlag = args.unInstall
	svcStartFlag = args.start
	svcStopFlag = args.stop

	// Mark as running as a service
	setServiceRun(args.foreground)

	if err := handleWindowsService(args.configPath); err != nil {
		log.Fatalf("failed to handle windows service: %v", err)
	}
}

func waitStop(cMonitorPid int) {
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

	confirmService()

loop:
	for {
		select {
		case <-sigs:
			sendServiceStop()
			break loop
		case serviceStop := <-CloseChan:
			if serviceStop {
				break loop
			}
		}
	}

	logger.JaLog("JAFRAMEWORK000003", logger.Logging{}, common.Manager.Name)
	doneChan := make(chan bool)
	wm.ProcessExitFlag = true
	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)
	}

	waitServiceClose()
	logger.JaLog("JAAGENT000002", logger.Logging{}, versionNo, revisionNo)

}

// func initWindowStackTrace() {
// 	C.setup_seh()
// }

//export goPrintStackTrace
// func goPrintStackTrace() {
// 	buf := make([]byte, 1024)
// 	n := runtime.Stack(buf, true)
// 	fmt.Printf("Go Stack Trace:\n%s", buf[:n])
// }

// func handlePanic() {
// 	if r := recover(); r != nil {
// 		logFile, err := os.OpenFile("panic.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
// 		if err != nil {
// 			fmt.Println("Could not open log file:", err)
// 			return
// 		}
// 		defer logFile.Close()

// 		fmt.Printf("Panic caught: %v\n", r)

// 		stackTrace := debug.Stack()

// 		log.SetOutput(logFile)
// 		log.Printf("Panic: %v\n", r)
// 		log.Printf("Stack Trace:\n%s", stackTrace)

// 		debug.PrintStack()

// 		os.Exit(1)
// 	}
// 	fmt.Printf("No panic occur. \n")
// }

// func setUnhandledExceptionFilter() {
// 	fmt.Printf("Call from Exception Filter \n")
// 	handler := syscall.NewCallback(exceptionHandler)
// 	procSetUnhandledExceptionFilter.Call(handler)
// }

// func exceptionHandler(exceptionInfo uintptr) uintptr {
// 	fmt.Println("[!] クラッシュ検出: ダンプ出力中...")

// 	dumpFile, err := os.Create("crash.dmp")
// 	if err != nil {
// 		fmt.Println("ダンプファイル作成失敗:", err)
// 		return 0
// 	}
// 	defer dumpFile.Close()

// 	hProcess, err := syscall.GetCurrentProcess()
// 	if err != nil {
// 		fmt.Println("GetCurrentProcess失敗:", err)
// 		return 0
// 	}

// 	pid := uint32(os.Getpid())

// 	ret, _, callErr := procMiniDumpWriteDump.Call(
// 		uintptr(hProcess),
// 		uintptr(pid),
// 		dumpFile.Fd(),
// 		MiniDumpWithFullMemory,
// 		exceptionInfo,
// 		0,
// 		0,
// 	)
// 	if ret == 0 {
// 		fmt.Println("MiniDumpWriteDump失敗:", callErr)
// 	} else {
// 		fmt.Println("クラッシュダンプ出力成功: crash.dmp")
// 	}

// 	return 0
// }

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

	var agentData common.AgentDataW
	var socket windows.Handle
	var tmpFile *os.File

	if data.EventRoute == common.TcpRoute {
		// Get raw Winsock SOCKET handle
		rawConn, err := data.NetConn.Conn.(*net.TCPConn).SyscallConn()
		if err != nil {
			return nil, err
		}
		defer data.NetConn.Close()

		rawConn.Control(func(fd uintptr) {
			socket = windows.Handle(fd)
		})

		// Create temporary file to share WSAProtocolInfo
		tmpFile, err = os.CreateTemp("", "wsaproto")
		if err != nil {
			return nil, err
		}
	}

	cmd := exec.Command(agentManagerExecPath, "-config-path", arg.configPath)

	stdinPipe, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}

	if err := cmd.Start(); err != nil {
		return nil, err
	}

	if eventRoute == common.TcpRoute {
		// Prepare WSAProtocolInfo
		childPID := uint32(cmd.Process.Pid)
		var protoInfo windows.WSAProtocolInfo

		ret, _, err := wsaDuplicateSocket.Call(
			uintptr(socket),
			uintptr(childPID),
			uintptr(unsafe.Pointer(&protoInfo)),
		)
		if ret != 0 {
			return nil, err
		}

		enc := gob.NewEncoder(tmpFile)
		if err := enc.Encode(protoInfo); err != nil {
			return nil, err
		}

		tmpFile.Close()
		agentData.SocketFilePath = tmpFile.Name()
	}

	agentData.EventData = data.EventData

	// Send event data
	agentDataBytes, err := json.Marshal(agentData)
	if err != nil {
		return nil, err
	}

	if _, err := stdinPipe.Write(agentDataBytes); err != nil {
		return nil, err
	}

	// Signal EOF to child
	if err := stdinPipe.Close(); err != nil {
		return nil, err
	}

	return cmd, nil
}

func sendSignal() {

}
