package transport

import (
	"context"
	"io"
	"os"
	"os/exec"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestStdio(t *testing.T) {
	r, w := Stdio()
	assert.Equal(t, os.Stdin, r)
	assert.Equal(t, os.Stdout, w)
}

func TestSpawnEcho(t *testing.T) {
	if os.Getenv("GO_TEST_PROCESS") == "1" {
		// Child process: read stdin, echo to stdout
		data, _ := io.ReadAll(os.Stdin)
		os.Stdout.Write(data)
		os.Exit(0)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Re-invoke ourselves as the child process
	cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=TestSpawnEcho")
	cmd.Env = append(os.Environ(), "GO_TEST_PROCESS=1")

	stdinPipe, err := cmd.StdinPipe()
	require.NoError(t, err)
	stdoutPipe, err := cmd.StdoutPipe()
	require.NoError(t, err)

	require.NoError(t, cmd.Start())

	// Write to subprocess stdin
	_, err = stdinPipe.Write([]byte("hello world\n"))
	require.NoError(t, err)
	stdinPipe.Close()

	// Read from subprocess stdout
	buf := make([]byte, 64)
	n, err := stdoutPipe.Read(buf)
	require.NoError(t, err)
	assert.Equal(t, "hello world\n", string(buf[:n]))

	cmd.Wait()
}

func TestSpawnWithConfig(t *testing.T) {
	// Use "echo" command as a simple subprocess
	proc, err := Spawn("echo", WithArgs("hello"), WithCaptureStderr(false))
	require.NoError(t, err)
	defer proc.Close()

	buf := make([]byte, 64)
	n, err := proc.Stdout.Read(buf)
	require.NoError(t, err)
	assert.Equal(t, "hello\n", string(buf[:n]))
}

func TestSpawnSubprocessClose(t *testing.T) {
	proc, err := Spawn("sleep", WithArgs("10"))
	require.NoError(t, err)

	start := time.Now()
	// Process will be killed after timeout since "sleep 10" won't exit on stdin close
	err = proc.CloseWithTimeout(500 * time.Millisecond)
	// Killed processes return a signal error, which is expected
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "signal")
	// Should not take the full 10 seconds
	assert.WithinDuration(t, start, time.Now(), 2*time.Second)
}

func TestSpawnProcessID(t *testing.T) {
	proc, err := Spawn("echo", WithArgs("test"))
	require.NoError(t, err)
	defer proc.Close()

	pid := proc.ProcessID()
	assert.Greater(t, pid, 0)
}

func TestBuildEnv(t *testing.T) {
	env := buildEnv(map[string]string{
		"MY_CUSTOM_VAR": "custom_value",
	})

	hasPath := false
	hasCustom := false
	for _, e := range env {
		if strings.HasPrefix(e, "PATH=") {
			hasPath = true
		}
		if e == "MY_CUSTOM_VAR=custom_value" {
			hasCustom = true
		}
	}
	assert.True(t, hasPath, "should preserve PATH")
	assert.True(t, hasCustom, "should include custom var")
}

func TestSpawnWithEnv(t *testing.T) {
	proc, err := Spawn("env",
		WithEnv(map[string]string{"ACP_TEST_VAR": "test123"}),
	)
	require.NoError(t, err)
	defer proc.Close()

	data, err := io.ReadAll(proc.Stdout)
	require.NoError(t, err)
	assert.Contains(t, string(data), "ACP_TEST_VAR=test123")
}
