package inject

import (
	"bufio"
	"debug/elf"
	"fmt"
	klog "github.com/sirupsen/logrus"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"strings"
	"syscall"
)

type LibNetInfo struct {
	LibName      string
	LibPath      string
	FuncSymbol   InstInfo
	FuncConvert0Symbol   InstInfo
	FuncGetTTLSymbol   InstInfo
	InnerSymbol  InnerSymbolInfo
	ProcLoadPath string
	FileDeleted  bool
	MapFile      string
}

type UprobeData struct {
	Offset  int
	Func    string
	ELFPath string
}

type JvmInjector struct {
	Pid               int
	ReleaseLibNetInfo LibNetInfo
	DebugLibNetInfo   LibNetInfo
	RecodeInfo        LibNetInfo
	// 原方法首个指令不为jmp | ReleaseLibNetInfo 读取无异常
	PreCheck struct {
		NeedInjectionCheck bool // 原指令校验 true表示可以继续执行注入
		LoadingCheck       bool // true 表示加载成功
		IoFdCheck          bool // fd地址校验
		NetSendFuncCheck   bool // netsend校验
		EbpfCanInjection   bool // 满足则注入ebpf
	}
	AfterCheck struct {
		IoFdCheck        bool
		NetSendFuncCheck bool
	}
	Uprobe UprobeData
	Rootfs string
}

func FindLibBaseByPathFromProcMaps(pid int, libPath string) (uint64, string, error) {
	mapsFile := fmt.Sprintf("/proc/%d/maps", pid)
	file, err := os.Open(mapsFile)
	if err != nil {
		return 0, "", err
	}
	defer file.Close()
	var start, end uint64
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		if strings.Contains(line, libPath) {
			fmt.Sscanf(line, "%x-%x", &start, &end)
			fields := strings.Fields(line)
			if len(fields) > 5 {
				path := fields[5]
				if strings.HasSuffix(path, ".so") {
					return start, path, nil
				}
			}
		}
	}

	return 1, "", fmt.Errorf("library %s not found in process.", libPath)
}

func FindLibBaseFromProcMaps(pid int, libName string) (uint64, string, string, bool, error) {
	mapsFile := fmt.Sprintf("/proc/%d/maps", pid)
	file, err := os.Open(mapsFile)
	if err != nil {
		return 0, "", "", false, err
	}
	defer file.Close()
	var start, end uint64
	var deleted bool
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		if strings.Contains(line, "/"+libName) {
			fmt.Sscanf(line, "%x-%x", &start, &end)
			fields := strings.Fields(line)
			if len(fields) > 5 {
				path := fields[5]
				if len(fields) > 6 && fields[6] == "(deleted)" {
					deleted = true
				}
				if strings.HasSuffix(path, ".so") {
					klog.Infof("[inject] found library in map %s", path)
					return start, path, fmt.Sprintf("/proc/%d/map_files/%s", pid, fields[0]), deleted, nil
				}
			}
		}
	}

	return 1, "", "", false, fmt.Errorf("[FindLibBaseFromProcMaps] library %s not found", libName)
}

func CopyFileAndMatchPermissions(srcFile, destFile, permFile string) error {
	// 获取权限参考文件的信息
	permInfo, err := os.Stat(permFile)
	if err != nil {
		return fmt.Errorf("failed to stat permission file: %w", err)
	}
	mode := permInfo.Mode()
	uid, gid := -1, -1
	if stat, ok := permInfo.Sys().(*syscall.Stat_t); ok {
		uid = int(stat.Uid)
		gid = int(stat.Gid)
	}

	srcInfo, err := os.Stat(srcFile)
	if err != nil {
		return fmt.Errorf("failed to stat source file/dir: %w", err)
	}

	if srcInfo.IsDir() {
		// 复制整个目录
		return filepath.WalkDir(srcFile, func(path string, d fs.DirEntry, err error) error {
			if err != nil {
				return err
			}
			relPath, err := filepath.Rel(srcFile, path)
			if err != nil {
				return err
			}
			targetPath := filepath.Join(destFile, relPath)

			if d.IsDir() {
				if err := os.MkdirAll(targetPath, mode); err != nil {
					return fmt.Errorf("failed to create directory %s: %w", targetPath, err)
				}
				if uid >= 0 && gid >= 0 {
					if err := os.Chown(targetPath, uid, gid); err != nil {
						return fmt.Errorf("failed to set directory ownership: %w", err)
					}
				}
			} else {
				if err := copyOneFile(path, targetPath, mode, uid, gid); err != nil {
					return fmt.Errorf("failed to copy file %s: %w", path, err)
				}
			}
			return nil
		})
	} else {
		// 复制单个文件
		return copyOneFile(srcFile, destFile, mode, uid, gid)
	}
}

func copyOneFile(src, dst string, mode os.FileMode, uid, gid int) error {
	in, err := os.Open(src)
	if err != nil {
		return fmt.Errorf("failed to open source file: %w", err)
	}
	defer in.Close()

	// 创建父目录
	if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
		return fmt.Errorf("failed to create parent directory: %w", err)
	}

	out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, mode)
	if err != nil {
		return fmt.Errorf("failed to create destination file: %w", err)
	}
	defer out.Close()

	if _, err := io.Copy(out, in); err != nil {
		return fmt.Errorf("failed to copy file content: %w", err)
	}

	if uid >= 0 && gid >= 0 {
		if err := out.Chown(uid, gid); err != nil {
			return fmt.Errorf("failed to set file ownership: %w", err)
		}
	}
	return nil
}
func GetFunctionOffset(libPath, functionName string) (elf.Symbol, error) {
	elfFile, err := elf.Open(libPath)
	if err != nil {
		return elf.Symbol{}, fmt.Errorf("failed to open ELF file: %v", err)
	}
	defer elfFile.Close()

	symbols, err := elfFile.DynamicSymbols()
	if err != nil {
		return elf.Symbol{}, fmt.Errorf("failed to read dynamic symbols: %v", err)
	}

	for _, sym := range symbols {
		if sym.Name == functionName {
			//fmt.Println("size:", sym.Size)
			return sym, nil
		}
	}

	//textSection := elfFile.Section(".text")
	//if textSection == nil {
	//	fmt.Println("textSection is null")
	//	//return nil
	//}
	//textSectionData, err := textSection.Data()
	//if err != nil {
	//	fmt.Println("textSectionData error is", err)
	//	//return nil
	//}
	//textSectionLen := uint64(len(textSectionData) - 1)

	return elf.Symbol{}, fmt.Errorf("function %s not found", functionName)
}
