package containers

import (
	"bufio"
	"bytes"
	"debug/elf"
	"errors"
	"fmt"
	"github.com/coroot/coroot-node-agent/flags"
	. "github.com/coroot/coroot-node-agent/utils/modelse"
	klog "github.com/sirupsen/logrus"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"unicode"
)

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

// resolveContainerPath 将容器内的绝对路径转换为宿主机上可访问的路径。
// 若 rootfs 非空（来自 Docker MergedDir 等），直接拼接；
// 否则通过内核提供的 /proc/<pid>/root 穿透进程的 mount namespace。
func resolveContainerPath(pid uint32, rootfs, inContainerPath string) string {
	if rootfs != "" {
		return rootfs + inContainerPath
	}
	return fmt.Sprintf("/proc/%d/root%s", pid, inContainerPath)
}

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
	} else if isPythonProcess(pid, rootfs) {
		return CodeTypePython
	} else if isNodeProcess(pid, rootfs) {
		return CodeTypeNode
	} else if isNginxProcess(pid, rootfs) {
		return CodeTypeNginx
	}
	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
	content, err := os.ReadFile(resolveContainerPath(pid, rootfs, exePath))
	if err != nil {
		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
	}

	ef, err := elf.Open(resolveContainerPath(pid, rootfs, path))
	if err != nil {
		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
	}

	ef, err := elf.Open(resolveContainerPath(pid, rootfs, path))
	if err != nil {
		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 isPythonProcess(pid uint32, rootfs string) bool {
	pathPrefix := rootfs
	if pathPrefix == "" && *flags.RunInContainer {
		pathPrefix = *flags.HostDirPathPrefix
	}

	procMapsPath := fmt.Sprintf("%s/proc/%d/maps", pathPrefix, pid)
	f, err := os.Open(procMapsPath)
	if err != nil {
		klog.Error(err)
		return false
	}
	defer f.Close()

	// 读取 maps（限制读取上限，防止非常大的文件）
	const maxRead = 256 * 1024 // 256 KB should be plenty for maps
	buf := make([]byte, maxRead)
	n, err := f.Read(buf)
	if err != nil && err != io.EOF {
		return false
	}
	data := buf[:n]
	lower := bytes.ToLower(data)

	// 快速子串预筛（避免不必要的正则）
	if !(bytes.Contains(lower, []byte("python")) ||
		bytes.Contains(lower, []byte("cpython")) ||
		bytes.Contains(lower, []byte("libpython"))) {
		return false
	}

	// 精确正则匹配：若任一匹配成功，则认定为 Python 进程
	var patterns = []*regexp.Regexp{
		// 可执行 /usr/bin/python 或 /usr/local/bin/python 等
		regexp.MustCompile(`(?i)/[a-z0-9_\-/.]*\bpython[0-9]*(\.[0-9]+)*\b`),
		// libpythonX.Y.so 或 libpython3.8.so.1.0 之类
		regexp.MustCompile(`(?i)libpython[0-9]*(\.[0-9]+)*\.so(\.[0-9]+)*`),
		// cpython-310 / cpython-39 等扩展模块名
		regexp.MustCompile(`(?i)cpython-[0-9]+`),
		// python3.10 的路径片段（更精确匹配版本）
		regexp.MustCompile(`(?i)python[0-9]+\.[0-9]+`),
	}

	for _, re := range patterns {
		if re.Find(lower) != nil {
			return true
		}
	}

	// 若没有任何精确模式匹配，则认为不是 Python（或无法确定）
	return false
}

func isNodeProcess(pid uint32, rootfs string) bool {

	// 1) readlink exe
	exePath, err := os.Readlink(fmt.Sprintf("/proc/%d/exe", pid))
	if err != nil {
		return false
	}

	// 2) 判断 exe 名称/路径是否为 node
	base := filepath.Base(exePath)
	lowBase := strings.ToLower(base)
	lowPath := strings.ToLower(exePath)
	if !(lowBase == "node" || lowBase == "nodejs" || strings.Contains(lowPath, "/node") || strings.Contains(lowPath, "nodejs")) {
		return false
	}

	// 3) 打开可执行文件并进行 strings 风格扫描
	f, err := os.Open(resolveContainerPath(pid, rootfs, exePath))
	if err != nil {
		// 无法打开二进制，视为未找到关键字
		return false
	}
	defer f.Close()

	// 要匹配的关键字正则（严格、降低误报）
	patterns := []*regexp.Regexp{
		regexp.MustCompile(`(?i)nodejsVersion`),
		regexp.MustCompile(`(?i)node_version`),
		regexp.MustCompile(`(?i)node[^\w]?js`),
	}

	checkMatch := func(s string) bool {
		for _, p := range patterns {
			if p.FindStringIndex(s) != nil {
				return true
			}
		}
		return false
	}

	const bufSize = 64 * 1024
	reader := bufio.NewReaderSize(f, bufSize)
	const minRunLen = 4
	var leftover []byte

	for {
		chunk := make([]byte, bufSize)
		n, rerr := reader.Read(chunk)
		if n > 0 {
			data := chunk[:n]
			if len(leftover) > 0 {
				data = append(leftover, data...)
				leftover = nil
			}
			start := -1
			for i, b := range data {
				if (b >= 32 && b < 127) || b == '\n' || b == '\t' || b == '\r' {
					if start == -1 {
						start = i
					}
				} else {
					if start != -1 {
						if i-start >= minRunLen {
							s := string(data[start:i])
							s = strings.TrimFunc(s, func(r rune) bool { return !unicode.IsPrint(r) })
							if checkMatch(s) {
								return true
							}
						}
						start = -1
					}
				}
			}
			if start != -1 {
				// 保存块尾部可打印残留
				leftover = append([]byte{}, data[start:]...)
				if len(leftover) > 1<<20 {
					leftover = leftover[len(leftover)-(1<<20):]
				}
			}
		}

		if rerr != nil {
			if errors.Is(rerr, io.EOF) {
				// 检查 leftover
				if len(leftover) >= minRunLen {
					s := string(leftover)
					s = strings.TrimFunc(s, func(r rune) bool { return !unicode.IsPrint(r) })
					if checkMatch(s) {
						return true
					}
				}
				// 读完未找到
				return false
			}
			// 读错误，返回 false
			return false
		}
	}
}

// isNginxProcess 判断进程是否为 nginx
// 通过 /proc/<pid>/exe 的 readlink 和 cmdline 检查
func isNginxProcess(pid uint32, rootfs string) bool {
	// 1) 通过 exe 路径判断
	exePath, err := os.Readlink(fmt.Sprintf("/proc/%d/exe", pid))
	if err != nil {
		return false
	}
	base := filepath.Base(exePath)
	if base == "nginx" {
		return true
	}

	// 2) 通过 cmdline 判断（nginx worker 进程的 exe 可能是被 delete 的路径）
	cmdline, err := os.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid))
	if err != nil {
		return false
	}
	lower := strings.ToLower(string(cmdline))
	if strings.Contains(lower, "nginx") {
		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
}
