package ebpftracer

import (
	"errors"
	"github.com/coroot/coroot-node-agent/common"
	"io/ioutil"
	"runtime"
	"strings"

	"debug/elf"
	"fmt"
	"path/filepath"

	"github.com/coroot/coroot-node-agent/proc"
	"github.com/coroot/coroot-node-agent/utils"

	"github.com/cilium/ebpf/link"
	"golang.org/x/arch/arm64/arm64asm"
	"golang.org/x/arch/x86/x86asm"
)

const (
	// goServeHTTP           = "net/http.serverHandler.ServeHTTP"
	// binPath           = "/root/code/jdk8u/build/linux-x86_64-normal-server-release/jdk/lib/amd64/libnio.so"
	symbolsocketRead0 = "Java_sun_nio_ch_FileDispatcherImpl_read0"
)

func (t *Tracer) AttachJavaNioReadUprobes(pid uint32, insID utils.ID, codeType common.CodeType) []link.Link {
	if t.disableL7Tracing {
		return nil
	}
	var links []link.Link
	var bpath = ""

	// JavaAOT 逻辑
	if codeType.IsJavaAotCode() {
		bpath = proc.Path(uint32(pid), "exe")
	} else {
		version := UsePIDToGetJDKVersion(pid)
		fmt.Println("java version is ", version)

		bpath = getSoPath(pid, "libnio.so")
		if bpath == "" {
			fmt.Println("can,t find the nio.so")
			return nil
		}
	}

	fmt.Println("find the nio.so path is ", bpath)
	ex, err := link.OpenExecutable(bpath)
	if err != nil {
		return nil
	}
	ef, err := elf.Open(bpath)
	if err != nil {
		return nil
		//PID:    int(pid),
	}
	defer ef.Close()

	symbols, err := ef.DynamicSymbols()
	if err != nil {
		fmt.Println("can not get symbols")
		if errors.Is(err, elf.ErrNoSymbols) {
			return nil
		}
		return nil
	}

	textSection := ef.Section(".text")
	if textSection == nil {
		fmt.Println("can not find .text section")
		return nil
	}
	textSectionData, err := textSection.Data()
	if err != nil {
		fmt.Println("can not get .text section data", err)
		return nil
	}
	textSectionLen := uint64(len(textSectionData) - 1)

	// opt := link.UprobeOptions{
	// 	Offset: 61,
	// }
	// upread02, err := ex.Uprobe(symbolsocketRead0, t.uprobes["uprobe_ret_Java_sun_nio_ch_FileDispatcherImpl_read0"], &opt)
	// if err != nil {
	// 	return nil
	// }
	// links = append(links, upread01)
	for _, s := range symbols {
		if elf.ST_TYPE(s.Info) != elf.STT_FUNC || s.Size == 0 {
			continue
		}
		switch s.Name {
		case symbolsocketRead0:
		default:
			continue
		}
		address := s.Value
		for _, p := range ef.Progs {
			if p.Type != elf.PT_LOAD || (p.Flags&elf.PF_X) == 0 {
				continue
			}

			if p.Vaddr <= s.Value && s.Value < (p.Vaddr+p.Memsz) {
				address = s.Value - p.Vaddr + p.Off
				break
			}
		}
		fmt.Println("s.Name-----:", s.Name)
		switch s.Name {
		case symbolsocketRead0:
			sStart := s.Value - textSection.Addr
			sEnd := sStart + s.Size
			if sEnd > textSectionLen {
				continue
			}
			sBytes := textSectionData[sStart:sEnd]
			returnOffsets := getCallNextMoveOffsets(ef.Machine, sBytes)
			if len(returnOffsets) == 0 {
				fmt.Println("failed to attach uprobe_ret_Java_sun_nio_ch_FileDispatcherImpl_read0 uprobe")
				return nil
			}
			for _, offset := range returnOffsets {
				l, err := ex.Uprobe(s.Name, t.uprobes["uprobe_ret_Java_sun_nio_ch_FileDispatcherImpl_read0"], &link.UprobeOptions{Address: address, Offset: uint64(offset), PID: int(pid)})
				if err != nil {
					fmt.Println("failed to attach uprobe_ret_Java_sun_nio_ch_FileDispatcherImpl_read0 uprobe")
					return nil
				}
				links = append(links, l)
			}
		}
	}

	if len(links) == 0 {
		return nil
	}
	fmt.Println("[jvm] libnio attached, pid is ", pid)
	return links
}

func (t *Tracer) AttachJavaNetWriteUprobes(pid uint32, insID utils.ID) []link.Link {
	if t.disableL7Tracing {
		return nil
	}
	var libnetSo = "/data/roger/graalvm/lib/libnet.so"
	var sys = "Java_java_net_SocketOutputStream_socketWrite0"
	var offset = 53
	if runtime.GOARCH == "arm64" {
		libnetSo = "/root/cwlibnet.so"
		sys = "CW_Java_java_net_SocketOutputStream_socketWrite0"
		offset = 8
	}
	var links []link.Link
	ex, err := link.OpenExecutable(libnetSo)
	if err != nil {
		return nil
	}
	opt := link.UprobeOptions{
		Offset: uint64(offset),
		PID:    int(pid),
	}
	upread02, err := ex.Uprobe(sys, t.uprobes["uprobe_Java_java_net_SocketOutputStream_socketWrite0"], &opt)
	if err != nil {
		return nil
	}
	links = append(links, upread02)

	if len(links) == 0 {
		return nil
	}
	fmt.Println("[jvm] libnet attached", pid)
	return links
}

func getCallNextMoveOffsets(machine elf.Machine, instructions []byte) []int {
	var res []int
	firstCall := 0
	switch machine {
	case elf.EM_X86_64:
		for i := 0; i < len(instructions); {
			ins, err := x86asm.Decode(instructions[i:], 64)
			if err == nil && ins.Op == x86asm.CALL {
				if firstCall == 0 {
					firstCall = 1
				} else {
					i += ins.Len
					res = append(res, i)
					return res
				}
			}
			i += ins.Len
		}
	case elf.EM_AARCH64:
		for i := 0; i < len(instructions); {
			ins, err := arm64asm.Decode(instructions[i:])
			if err == nil && ins.Op == arm64asm.BL {
				if firstCall == 0 {
					firstCall = 1
				} else {
					i += 4
					res = append(res, i)
					return res
				}
			}
			i += 4
		}
	}
	return res
}

func getSoPath(pid uint32, soname string) string {
	// 获取进程的maps文件路径
	mapsPath := fmt.Sprintf("/proc/%d/maps", pid)

	// 读取maps文件内容
	mapsData, err := ioutil.ReadFile(mapsPath)
	if err != nil {
		fmt.Println("lookup so error.")
		return ""
	}

	lines := strings.Split(string(mapsData), "\n")
	for _, line := range lines {
		fields := strings.Fields(line)
		if len(fields) >= 6 {
			perms := fields[1]
			path := fields[len(fields)-1]

			if strings.Contains(perms, "x") && filepath.Ext(path) == ".so" {
				if strings.Contains(path, soname) {
					return path
				}
			}
		}
	}
	return ""
}
