package winpwn

import (
	"bufio"
	"bytes"
	"fmt"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
)

// NewROPExternal is the explicit-tool-path variant of NewROP, for an rp-win
// build kept somewhere other than RP_WIN_EXE/defaultRPWinTool, or a
// different rp++ fork entirely (same output format).
func NewROPExternal(binaryPath string, toolPath string) (*ROP, error) {
	return newROP(binaryPath, toolPath)
}

func (r *ROP) findGadgetsExternal() error {
	// -f: target file
	// -r 5: max gadget length in instructions
	// --unique: dedupe identical instruction sequences
	// --allow-branches: also report gadgets terminated by an indirect
	// jmp/call (register or memory operand) and jmp-$ self-loops, not just
	// ret/ret-imm16 -- the JOP transit primitives needed when the target
	// has no clean `pop reg ; ret` for a given register.
	cmd := exec.Command(r.toolPath, "-f", r.binaryPath, "-r", "5", "--unique", "--allow-branches")

	var out, stderr bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &stderr

	if err := cmd.Run(); err != nil {
		return fmt.Errorf("failed to run gadget finder %q: %w\n%s", r.toolPath, err, stderr.String())
	}

	// Регулярное выражение для парсинга вывода rp++
	// Пример строки: "0x140001234: pop rcx ; ret ;  (1 found)"
	re := regexp.MustCompile(`^(0x[0-9a-fA-F]+):\s*(.+?)\s*(?:;\s*\(\d+ found\))?$`)

	scanner := bufio.NewScanner(&out)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())

		matches := re.FindStringSubmatch(line)
		if len(matches) >= 3 {
			addrStr := matches[1]
			instructions := matches[2]

			// Конвертация hex-строки в uint64
			addr, err := strconv.ParseUint(strings.TrimPrefix(addrStr, "0x"), 16, 64)
			if err == nil {
				r.gadgets = append(r.gadgets, Gadget{
					Address:      addr,
					Instructions: instructions,
				})
			}
		}
	}

	return nil
}

// Search ищет гаджет по подстроке (например, "pop rcx ; ret")
func (r *ROP) Search(instr string) ([]Gadget, error) {
	var results []Gadget
	searchStr := strings.ToLower(instr)

	for _, g := range r.gadgets {
		if strings.Contains(strings.ToLower(g.Instructions), searchStr) {
			results = append(results, g)
		}
	}

	if len(results) == 0 {
		return nil, fmt.Errorf("gadget '%s' not found", instr)
	}
	return results, nil
}
