package containers

import (
	"debug/elf"
	"fmt"
	"github.com/coroot/coroot-node-agent/flags"
	. "github.com/coroot/coroot-node-agent/utils/modelse"
	klog "github.com/sirupsen/logrus"
	"os"
	"os/exec"
	"regexp"
	"runtime"
	"strings"
)

var libjvmRegex = regexp.MustCompile(`.*/libjvm\.so`)

func GetExeType(pid uint32, rootfs string) CodeType {
	mapsFilePath := fmt.Sprintf("%sproc/%d/maps", "/", pid)

	data, err := os.ReadFile(mapsFilePath)
	if err != nil {
		klog.WithError(err).Errorf("Failed to read %s: %s", mapsFilePath)
		return CodeTypeUnknown
	}

	content := string(data)

	if libjvmRegex.MatchString(content) {
		//fmt.Println("is java process")
		if isJavaAotProcess(pid, rootfs) {
			//fmt.Println("is javaAot process")
			return CodeTypeJavaAot
		}
		return CodeTypeJava
	} else if isJavaAotProcess(pid, rootfs) {
		//fmt.Println("is javaAot process")
		return CodeTypeJavaAot
	} else if isGoProcess(pid, rootfs) {
		//fmt.Println("is go process")
		return CodeTypeGo
	} else if isNetCoreProcess(pid, rootfs) {
		//	fmt.Println("is netcore process")
		return CodeTypeNetCoreAot
	}
	return CodeTypeUnknown
}

// isJavaAotProcess checks if the process with the given PID is a GraalVM native image
func isJavaAotProcess(pid uint32, rootfs string) bool {
	// Get the executable path for the given PID
	exePath, err := os.Readlink(fmt.Sprintf("%sproc/%d/exe", "/", pid))
	if err != nil {
		//fmt.Printf("Error reading executable path for PID %d: %v\n", pid, err)
		klog.WithError(err).Errorf("isJavaAotProcess,failed to reading executable path for PID [%d]", pid)
		return false
	}

	// Read the content of the executable file
	pathPrefix := rootfs
	if pathPrefix == "" && *flags.RunInContainer {
		pathPrefix = *flags.HostDirPathPrefix
	}
	content, err := os.ReadFile(fmt.Sprintf("%s%s", pathPrefix, exePath))
	if err != nil {
		if _, err = os.Stat(exePath); err == nil {
			content, err = os.ReadFile(exePath)
			if err != nil {
				klog.WithError(err).Errorf("isJavaAotProcess,failed to reading executable file local for PID [%d]", pid)
				return false
			}
		} else {
			klog.WithError(err).Errorf("isJavaAotProcess,failed to reading executable file for PID [%d]", pid)
			return false
		}
	}

	// Check if the file contains the "graal_attach_thread" string
	if strings.Contains(string(content), "graal_attach_thread") {
		return true
	}

	return false
}

func isNetCoreProcess(pid uint32, rootfs string) bool {
	path, err := getProcessPath(pid)
	if err != nil {
		//fmt.Printf("无法获取进程路径：%s\n", err)
		klog.WithError(err).Errorf("isNetCoreProcess,failed to open as elf binary path for PID [%d]", pid)
		return false
	}

	pathPrefix := rootfs
	if pathPrefix == "" && *flags.RunInContainer {
		pathPrefix = *flags.HostDirPathPrefix
	}
	ef, err := elf.Open(pathPrefix + path)
	if err != nil {
		if _, err = os.Stat(path); err == nil {
			ef, err = elf.Open(path)
			if err != nil {
				klog.WithError(err).Errorf("isNetCoreProcess,failed to open as elf binary file local for PID [%d]", pid)
				return false
			}
		} else {
			klog.WithError(err).Errorf("isNetCoreProcess,failed to open as elf binary file for PID [%d]", pid)
			return false
		}
	}
	defer ef.Close()

	__managedcode := ef.Section("__managedcode")
	if __managedcode != nil {
		fmt.Println("is a netcore process")
		return true
	}
	return false
}

func isGoProcess(pid uint32, rootfs string) bool {
	path, err := getProcessPath(pid)
	if err != nil {
		klog.WithError(err).Errorf("isGoProcess,failed to open as elf binary path for PID [%d]", pid)
		return false
	}

	pathPrefix := rootfs
	if pathPrefix == "" && *flags.RunInContainer {
		pathPrefix = *flags.HostDirPathPrefix
	}

	ef, err := elf.Open(pathPrefix + path)
	if err != nil {
		if _, err = os.Stat(path); err == nil {
			ef, err = elf.Open(path)
			if err != nil {
				klog.WithError(err).Errorf("isGoProcess,failed to open as elf binary file local for PID [%d]", pid)
				return false
			}
		} else {
			klog.WithError(err).Errorf("isGoProcess,failed to open as elf binary file for PID [%d]", pid)
			return false
		}
	}
	defer ef.Close()

	gopclntabSection := ef.Section(".gopclntab")
	if gopclntabSection != nil {
		//fmt.Println("is a go process")
		return true
	}
	return false
}

func getProcessPath(pid uint32) (string, error) {
	switch runtime.GOOS {
	case "linux":
		return getLinuxProcessPath(pid)
	default:
		return "", fmt.Errorf("不支持的操作系统：%s", runtime.GOOS)
	}
}

func getLinuxProcessPath(pid uint32) (string, error) {
	procPath := fmt.Sprintf("/proc/%d/exe", pid)
	path, err := os.Readlink(procPath)
	if err != nil {
		return "", err
	}
	return path, nil
}

func executeCommand(name string, args ...string) (string, error) {
	cmd := exec.Command(name, args...)
	out, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return string(out), nil
}
