package ebpftracer

import (
	"bufio"
	"errors"
	"io"
	"log"
	"os"
	"strconv"
	"strings"

	"debug/dwarf"
	"debug/elf"
	"fmt"

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

	"github.com/cilium/ebpf/link"
)

const (
	// goServeHTTP           = "net/http.serverHandler.ServeHTTP"
	// binPath           = "/root/code/jdk8u/build/linux-x86_64-normal-server-release/jdk/lib/amd64/libnio.so"
	libPath                  = "/data/roger/NET8/CoreAoT/bin/Release/net8.0/linux-x64/publish/CoreAoT"
	netcoresymbolsocketRead0 = "SystemNative_Receive"
)

type MemoryMap struct {
	Start, End uint64
}

func ReadFirstLineOfMapsFile(pid string) (*MemoryMap, error) {
	file, err := os.Open(fmt.Sprintf("/proc/%s/maps", pid))
	if err != nil {
		return nil, err
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	if scanner.Scan() {
		fields := strings.Fields(scanner.Text())
		addresses := strings.Split(fields[0], "-")
		if len(addresses) != 2 {
			return nil, errors.New("unexpected format in /proc/<pid>/maps")
		}

		start, err := strconv.ParseUint(addresses[0], 16, 64)
		if err != nil {
			return nil, err
		}

		end, err := strconv.ParseUint(addresses[1], 16, 64)
		if err != nil {
			return nil, err
		}

		return &MemoryMap{
			Start: start,
			End:   end,
		}, nil
	}

	if err := scanner.Err(); err != nil {
		return nil, err
	}

	return nil, errors.New("empty /proc/<pid>/maps")
}

func (t *Tracer) getFunctionOffsetDBG(libPath, functionName string) (elf.Symbol, error) {
	dwarfFile, err := elf.Open(libPath)
	dwarfData, err := dwarfFile.DWARF()
	if err != nil {
		log.Fatal(err)
	}

	type uprobesDef struct {
		Name       string
		Offset     uint64
		EntAddress uint64
		RetAddress uint64
	}

	listEntry := make(map[dwarf.Offset]uprobesDef)
	SpecListEntry := []dwarf.Entry{}

	entryReader := dwarfData.Reader()

	for {
		entry, err := entryReader.Next()
		if err == io.EOF {
			// We've reached the end of DWARF entries
			break
		}
		if err != nil {
			log.Fatalf("Error reading entry: %v", err)
		}
		if entry == nil {
			log.Println("Warning: a nil entry was returned with no error")
			break
		}
		if entry.Tag == dwarf.TagSubprogram {
			// fmt.Printf("entry address: %x, %d\n", entry.Offset, entry.Children)
			funName, _ := entry.Val(dwarf.AttrName).(string)
			if functionName == funName {
				entAddress, _ := entry.Val(dwarf.AttrLowpc).(uint64)
				retAddress, _ := entry.Val(dwarf.AttrHighpc).(uint64)
				fmt.Printf("Function %s address: %x, %x, %x\n", funName, entAddress, entry.Offset, retAddress)
				uprobes := uprobesDef{}
				uprobes.EntAddress = entAddress
				uprobes.RetAddress = retAddress
				uprobes.Offset = uint64(entry.Offset)
				uprobes.Name = funName

				listEntry[entry.Offset] = uprobes
			}

			specAddr, _ := entry.Val(dwarf.AttrSpecification).(dwarf.Offset)
			lowpc := entry.Val(dwarf.AttrLowpc)
			if lowpc != nil && specAddr > 0 && lowpc.(uint64) > 0 {
				// fmt.Printf("AttrSpecification address: %x, %x\n", specAddr, entry.Offset)
				SpecListEntry = append(SpecListEntry, *entry)
			}
		}
	}

	for _, v := range SpecListEntry {
		specAddr, _ := v.Val(dwarf.AttrSpecification).(dwarf.Offset)
		// fmt.Printf("SpecListEntrySpecListEntrySpecListEntry Attach Function: %x\n", specAddr)
		_, ok := listEntry[specAddr]
		if ok {
			vv := listEntry[specAddr]
			entAddr := v.Val(dwarf.AttrLowpc)
			if entAddr != nil {
				vv.EntAddress = entAddr.(uint64)
			}
			retAddr := v.Val(dwarf.AttrHighpc)
			if retAddr != nil {
				switch retAddr.(type) {
				case uint64:
					vv.RetAddress = uint64(retAddr.(uint64))
				case int64:
					vv.RetAddress = uint64(retAddr.(int64))
				default:
					fmt.Println("Unknown type")
				}
			}
			listEntry[specAddr] = vv
		}
	}

	for _, v := range listEntry {
		sSize := v.RetAddress
		if v.RetAddress > v.EntAddress {
			sSize = v.RetAddress - v.EntAddress
		}
		symbol := elf.Symbol{}
		symbol.Name = v.Name
		symbol.Value = v.EntAddress
		symbol.Size = 180
		fmt.Printf("Need Attach Function %s address: %x, %x, %d\n", v.Name, v.EntAddress, v.RetAddress, sSize)
		return symbol, nil
	}
	return elf.Symbol{}, fmt.Errorf("function %s not found", functionName)
}

func (t *Tracer) AttachNetCoreNetReadUprobes(pid uint32, insID utils.ID) []link.Link {
	if t.disableL7Tracing {
		return nil
	}

	var links []link.Link
	ex, err := link.OpenExecutable(libPath)

	// 获取函数的偏移量
	functionSym, err := t.getFunctionOffsetDBG(libPath+".dbg", netcoresymbolsocketRead0)

	l, err := ex.Uprobe(functionSym.Name, t.uprobes["SystemNative_Receive"], &link.UprobeOptions{Address: functionSym.Value, Offset: 113})
	if err != nil {
		fmt.Println("failed to attach SystemNative_Receive uprobe", err, functionSym)
		return nil
	}

	links = append(links, l)

	if len(links) == 0 {
		return nil
	}
	fmt.Println("netcore uprobes attached, pid is ", pid)
	return links
}

func (t *Tracer) AttachNetCoreNetWriteUprobes(pid uint32, insID utils.ID) []link.Link {
	if t.disableL7Tracing {
		return nil
	}
	//
	//if pid != 251719 {
	//	return nil
	//}

	var libnetSo = "/data/roger/han/libmylib.so"
	var sys = "asmnop"
	var links []link.Link
	ex, err := link.OpenExecutable(libnetSo)
	if err != nil {
		return nil
	}
	opt := link.UprobeOptions{
		Offset: 16,
		PID:    int(pid),
	}
	upread02, err := ex.Uprobe(sys, t.uprobes["netcore_asmnop"], &opt)
	if err != nil {
		return nil
	}
	links = append(links, upread02)

	if len(links) == 0 {
		return nil
	}
	fmt.Println("netcore client uprobes attached", pid)

	return links
}
