package ebpftracer

import (
	"bufio"
	"fmt"
	"github.com/cilium/ebpf"
	"github.com/cilium/ebpf/link"
	"github.com/coroot/coroot-node-agent/ebpftracer/tracer"
	"github.com/coroot/coroot-node-agent/ebpftracer/tracer/jattach"
	"github.com/coroot/coroot-node-agent/utils"
	. "github.com/coroot/coroot-node-agent/utils/modelse"
	klog "github.com/sirupsen/logrus"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
)

// AttachStackUprobes
// default process stack
func (t *Tracer) AttachStackUprobes(path string, uprobes []tracer.Uprobe) []link.Link {
	var links []link.Link

	ex, err := link.OpenExecutable(path)
	if err != nil {
		return nil
	}
	klog.Infoln("[stack] Attach Start", path)

	for i, up := range uprobes {
		klog.Debugf("[stack] attaching %d -> %d -> %s -> 0x%x -> 0x%x -> 0x%x", i, len(uprobes), up.Funcname, up.AbsOffset, up.Address, up.AbsOffset+up.Address)
		var prog *ebpf.Program
		switch up.Location {
		case tracer.AtEntry:
			prog = t.uprobes["ent"]
		case tracer.AtRet:
			prog = t.uprobes["ret"]
		case tracer.AtDotNetEntry:
			prog = t.uprobes["dotnetent"]
		}
		uplink, err := ex.Uprobe(up.Funcname, prog, &link.UprobeOptions{Address: up.Address, Offset: up.AbsOffset})
		if err != nil {
			klog.Errorf("[stack] attachingERROR:%v, %v, %v", err, up, uplink)
			// return nil
		} else {
			links = append(links, uplink)
		}
	}
	return links
}

// JVM process stack
func (t *Tracer) JattachJvm(pid uint32, appInfo AppInfo, whiteList, blackList, rootfs string) error {
	klog.Infoln("[Jvm stack uprobe] Attach Start AttachJVMStackUprobes", pid)
	// TODO tiny Agent 注入
	// TODO copy agent到 jre conf/lib/plugins

	//argkv := "/opt/github/euspace/dist/package_dir/agents/NativeAgent/lib/apmAgent.jar=/opt/github/euspace/dist/package_dir/agents/NativeAgent"
	if whiteList == "" && blackList == "" {
		return fmt.Errorf("no stack rule")
	}
	whiteList = strings.ReplaceAll(whiteList, ",", "")
	blackList = strings.ReplaceAll(blackList, ",", "")
	var stackRule string
	if whiteList == "" {
		stackRule = fmt.Sprintf("blackStack=%s", blackList) // 仅保留黑名单
	} else if blackList == "" {
		stackRule = fmt.Sprintf("whiteStack=%s", whiteList) // 仅保留白名单
	} else {
		stackRule = fmt.Sprintf("whiteStack=%s,blackStack=%s", whiteList, blackList)
	}
	//nativeBasePath := utils.GetDefaultAgentsPath("NativeAgent")
	nativeBasePath := "/tmp/NativeAgent"
	kvPairs := []string{
		fmt.Sprintf("%s=%s", filepath.Join(nativeBasePath, "lib", "apmAgent.jar"), nativeBasePath),
		fmt.Sprintf("%s=%d", "appId", appInfo.AppIdHash.IntVal),
		fmt.Sprintf("%s=%d", "agentId", appInfo.AgentId),
		fmt.Sprintf("%s=%d", "hostId", utils.GetHostID()),
		fmt.Sprintf("%s=%d", "accountId", utils.GetAccountID()),
		stackRule,
	}
	argkv := strings.Join(kvPairs, ",")
	klog.Infof("[Jvm stack uprobe] params:[%s]", argkv)

	args := []string{"load", "instrument", "false", argkv}
	jattacher := jattach.JvmJattacher{
		Pid:         pid,
		Args:        args,
		PrintOutput: 1,
	}

	res, err := jattacher.JAttach()
	klog.Infof("[Jvm stack uprobe] JAttach Result: %d", res)
	if err != nil {
		return err
	}

	return nil
}

func (t *Tracer) AttachJVMStackUprobes(pid uint32, appInfo AppInfo, rootfs string) ([]link.Link, error) {

	//path = utils.GetDefaultAgentsPath("NativeAgent", "libnativeAgent.so")
	//tmp/NativeAgentSo2297066477572820801.tmp
	path, err := FindNativeSoFromMapped(pid, "NativeAgentSo", ".tmp")
	if err != nil {
		klog.Error(err)
		return nil, err
	}
	path = filepath.Join(rootfs, path)

	setNodeEnter := "Java_com_cloudwise_agent_common_natives_TraceNative_setNodeEnter"
	setNodeReturn := "Java_com_cloudwise_agent_common_natives_TraceNative_setNodeReturn"

	klog.Infoln("[Jvm stack uprobe] Attach Start AttachJVMStackUprobes", path)

	var links []link.Link
	ex, err := link.OpenExecutable(path)
	if err != nil {
		klog.Error(err)
		return nil, err
	}

	klog.Infof("[Jvm stack uprobe] Attach Start [%s] [%s] ", setNodeEnter, setNodeReturn)
	uplink, err := ex.Uprobe(setNodeEnter, t.uprobes["setNodeEnter"], &link.UprobeOptions{Offset: 0x0, PID: int(pid)})
	if err != nil {
		klog.Errorf("[Jvm stack uprobe] Attaching ERROR: %v, %v, %v\n", err, setNodeEnter, uplink)
		return nil, err
	} else {
		links = append(links, uplink)
	}

	klog.Infoln("Attach Start " + setNodeReturn)
	uplink, err = ex.Uprobe(setNodeReturn, t.uprobes["setNodeReturn"], &link.UprobeOptions{Offset: 0x0})
	if err != nil {
		klog.Errorf("[Jvm stack uprobe] Attaching ERROR: %v, %v, %v\n", err, setNodeReturn, uplink)
		return nil, err
	} else {
		links = append(links, uplink)
	}
	return links, nil
}

func FindNativeSoFromMapped(pid uint32, prefix, suffix string) (string, error) {
	mapsFile := fmt.Sprintf("/proc/%d/maps", pid)
	tmpFile, err := os.Open(mapsFile)
	if err != nil {
		return "", fmt.Errorf("error opening maps file: %v", err)
	}
	defer tmpFile.Close()

	var selectedPath string
	var maxTimestamp int64
	fallbackPaths := make(map[string]struct{}) // 使用 map 去重

	scanner := bufio.NewScanner(tmpFile)
	for scanner.Scan() {
		line := scanner.Text()
		if strings.Contains(line, prefix) && strings.HasSuffix(line, suffix) {
			parts := strings.Fields(line)
			if len(parts) > 5 {
				path := parts[len(parts)-1]
				baseName := filepath.Base(path)

				if strings.HasPrefix(baseName, prefix) && strings.HasSuffix(baseName, suffix) {
					middle := strings.TrimSuffix(strings.TrimPrefix(baseName, prefix), suffix)
					segments := strings.Split(middle, ".")
					if len(segments) >= 2 {
						timestampStr := segments[len(segments)-1]
						timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
						if err == nil && timestamp > maxTimestamp {
							maxTimestamp = timestamp
							selectedPath = path
						}
					} else {
						fallbackPaths[path] = struct{}{}
					}
				}
			}
		}
	}

	if err = scanner.Err(); err != nil {
		return "", fmt.Errorf("error reading maps file: %v", err)
	}

	if selectedPath != "" {
		return selectedPath, nil
	}

	var latestPath string
	var latestModTime int64
	for path := range fallbackPaths {
		info, err := os.Stat(path)
		if err != nil {
			continue
		}
		stat, ok := info.Sys().(*syscall.Stat_t)
		if !ok {
			continue
		}
		modTime := stat.Mtim.Sec
		if modTime > latestModTime {
			latestModTime = modTime
			latestPath = path
		}
	}

	if latestPath != "" {
		return latestPath, nil
	}

	return "", fmt.Errorf("no matching path found")
}
