package tracer

import (
	"debug/dwarf"
	"debug/elf"
	"errors"
	"fmt"
	"github.com/cilium/ebpf"
	"github.com/coroot/coroot-node-agent/utils"
	. "github.com/coroot/coroot-node-agent/utils/modelse"
	"github.com/coroot/coroot-node-agent/utils/try"
	klog "github.com/sirupsen/logrus"
	"io"
	"net"
	"strings"
)

const (
	offsetInferServerAddr = "127.0.0.1"
	offsetInferServerPort = 54583
	bufferSize            = 16
)

type ID struct {
	// ModPath is the module path containing the struct field package.
	//
	// If set to "std", the struct field belongs to the standard Go library.
	ModPath string
	// PkgPath package import path containing the struct field.
	PkgPath string
	// Struct is the name of the struct containing the field.
	Struct string
	// Field is the field name.
	Field string
}

func kernelOffsetInferServer(listener net.Listener) error {

	klog.Info("[eBPF Kernel Adapt] kernel_offset_infer_server started.")

	//cpuOnlineCount := runtime.NumCPU()

	try.Go(func() {
		for {
			conn, err := listener.Accept()
			if err != nil {
				klog.Errorf("[eBPF Kernel Adapt] Fail to accept client request: %v", err)
				return
			}
			//go handleConnection(conn)
			try.GoParams(handleConnection, utils.CatchFn, conn)
		}
	}, utils.CatchFn)

	return nil
}

func handleConnection(conn net.Conn) {
	defer conn.Close()

	buffer := make([]byte, bufferSize)
	for {
		n, err := conn.Read(buffer)
		if err != nil {
			if err == io.EOF {
				klog.Errorf("[eBPF Kernel Adapt] client connection closed: %v", err)
				return
			}
			klog.Errorf("[eBPF Kernel Adapt] Error reading from connection: %v", err)
			return
		}
		//if n == 0 {
		//	*clientCount++
		//	break
		//}

		request := strings.TrimSpace(string(buffer[:n]))
		klog.Infof("[eBPF Kernel Adapt] Request received: %v", request)
		if request == "hello" {
			_, err := conn.Write([]byte("OK"))
			if err != nil {
				klog.Errorf("[eBPF Kernel Adapt] Error writing response: %v", err)
				break
			}
		}
	}
}

func kernelOffsetInferClient() error {
	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", offsetInferServerAddr, offsetInferServerPort))
	if err != nil {
		return fmt.Errorf("failed to connect to server: %v", err)
	}
	defer conn.Close()

	klog.Infoln("[eBPF Kernel Adapt] kernel_offset_infer_client started.")

	request := "hello"
	_, err = conn.Write([]byte(request))
	if err != nil {
		return fmt.Errorf("failed to send request to server: %v", err)
	}

	buffer := make([]byte, bufferSize)
	for {
		n, err := conn.Read(buffer)
		if err != nil {
			klog.Errorf("[eBPF Kernel Adapt] Error reading from connection: %v", err)
			break
		}
		if n == 0 {
			break
		}

		response := strings.TrimSpace(string(buffer[:n]))
		klog.Infof("[eBPF Kernel Adapt] Response received: %v", response)
		if response == "OK" {
			break
		}
	}

	klog.Infoln("[eBPF Kernel Adapt] kernel_offset_infer_client finished.")
	return nil
}

func NewID(mod, pkg, strct, field string) ID {
	return ID{ModPath: mod, PkgPath: pkg, Struct: strct, Field: field}
}

func gotoEntry(r *dwarf.Reader, tag dwarf.Tag, name string) bool {
	_, err := findEntry(r, tag, name)
	return err == nil
}

// findEntry returns the DWARF entry with a tag equal to name read from r. An
// error is returned if the entry cannot be found.
func findEntry(r *dwarf.Reader, tag dwarf.Tag, name string) (*dwarf.Entry, error) {
	for {
		entry, err := r.Next()
		if err == io.EOF || entry == nil {
			break
		}

		if entry.Tag == tag {
			if f, ok := entryField(entry, dwarf.AttrName); ok {
				if name == f.Val.(string) {
					return entry, nil
				}
			}
		}
	}
	return nil, errors.New("not found")
}

// entryField returns the DWARF field from DWARF entry e that has the passed
// DWARF attribute a.
func entryField(e *dwarf.Entry, a dwarf.Attr) (dwarf.Field, bool) {
	for _, f := range e.Field {
		if f.Attr == a {
			return f, true
		}
	}
	return dwarf.Field{}, false
}

func GetOffset(id ID, path string) (uint64, bool) {

	strct := fmt.Sprintf("%s.%s", id.PkgPath, id.Struct)

	elfF, err := elf.Open(path)
	if err != nil {
		klog.Error(err)
		return 0, false
	}
	defer elfF.Close()

	data, err := elfF.DWARF()
	//fmt.Println(err)
	r := data.Reader()
	if !gotoEntry(r, dwarf.TagStructType, strct) {
		return 0, false
	}

	e, err := findEntry(r, dwarf.TagMember, id.Field)
	if err != nil {
		klog.Error(err)
		return 0, false
	}

	f, ok := entryField(e, dwarf.AttrDataMemberLoc)
	if !ok {
		return 0, false
	}

	return uint64(f.Val.(int64)), true
}

func UpdateProcInfoToMap(collection *ebpf.Collection, pid uint32, proc any) (int, error) {
	return bpf_table_set_value(collection, MAP_PROC_INFO_MAP_NAME, pid, proc)
}

func DelProcInfoFromMap(collection *ebpf.Collection, pid uint32) (int, error) {
	return bpf_table_delete_key(collection, MAP_PROC_INFO_MAP_NAME, pid)
}
