package debugdetect

import (
	"bufio"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"testing"

	protest "github.com/go-delve/delve/pkg/proc/test"
	"github.com/go-delve/delve/service/rpc2"
)

func TestIntegration_NotAttached(t *testing.T) {
	// Build the fixture
	fixturesDir := protest.FindFixturesDir()
	fixtureSrc := filepath.Join(fixturesDir, "debugdetect.go")
	fixtureBin := filepath.Join(t.TempDir(), "debugdetect")
	if runtime.GOOS == "windows" {
		fixtureBin += ".exe"
	}

	cmd := exec.Command("go", "build", "-o", fixtureBin, fixtureSrc)
	if out, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("failed to build fixture: %v\n%s", err, out)
	}

	// Run the fixture (not under debugger)
	cmd = exec.Command(fixtureBin)
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("fixture failed: %v\n%s", err, out)
	}

	output := string(out)
	if !strings.Contains(output, "NOT_ATTACHED") {
		t.Errorf("expected 'NOT_ATTACHED' in output, got: %s", output)
	}
}

func TestIntegration_WaitForDebugger(t *testing.T) {
	// This test verifies that WaitForDebugger() blocks until a debugger
	// attaches and then returns successfully by running the fixture
	// under Delve.
	protest.AllowRecording(t)

	dlvbin := protest.GetDlvBinary(t)
	fixturesDir := protest.FindFixturesDir()
	fixtureSrc := filepath.Join(fixturesDir, "waitfordebugger.go")

	const listenAddr = "127.0.0.1:40581"
	cmd := exec.Command(dlvbin, "debug", fixtureSrc, "--headless", "--continue", "--accept-multiclient", "--listen", listenAddr)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		t.Fatal(err)
	}
	defer stdout.Close()

	if err := cmd.Start(); err != nil {
		t.Fatal(err)
	}

	// Read stdout until we see the program output
	scanner := bufio.NewScanner(stdout)
	foundOutput := false
	for scanner.Scan() {
		line := scanner.Text()
		t.Log(line)
		if strings.Contains(line, "DEBUGGER_FOUND") {
			foundOutput = true
			break
		}
	}

	// Clean up - connect and detach
	client := rpc2.NewClient(listenAddr)
	client.Detach(true)
	cmd.Wait()

	if !foundOutput {
		t.Error("expected 'DEBUGGER_FOUND' in output when running under debugger")
	}
}

func TestIntegration_Attached(t *testing.T) {
	// This test verifies that IsDebuggerAttached() returns true when
	// the process is actually running under a debugger by using
	// Delve to debug the fixture source file
	protest.AllowRecording(t)

	dlvbin := protest.GetDlvBinary(t)
	fixturesDir := protest.FindFixturesDir()
	fixtureSrc := filepath.Join(fixturesDir, "debugdetect.go")

	// Run the fixture under dlv debug with --headless --continue
	// This will attach the debugger, compile and run the program
	const listenAddr = "127.0.0.1:40580"
	cmd := exec.Command(dlvbin, "debug", fixtureSrc, "--headless", "--continue", "--accept-multiclient", "--listen", listenAddr)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		t.Fatal(err)
	}
	defer stdout.Close()

	if err := cmd.Start(); err != nil {
		t.Fatal(err)
	}

	// Read stdout until we see the program output
	scanner := bufio.NewScanner(stdout)
	foundOutput := false
	for scanner.Scan() {
		line := scanner.Text()
		t.Log(line)
		if strings.Contains(line, "ATTACHED") {
			foundOutput = true
			break
		}
	}

	// Clean up - connect and detach
	client := rpc2.NewClient(listenAddr)
	client.Detach(true)
	cmd.Wait()

	if !foundOutput {
		t.Error("expected 'ATTACHED' in output when running under debugger")
	}
}
