// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2026-present Datadog, Inc.

package testcmd_test

import (
	"context"
	"math"
	"os"
	"path/filepath"
	"strconv"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"

	"github.com/DataDog/rshell/interp"
)

func mustNotHang(t *testing.T, f func()) {
	t.Helper()
	done := make(chan struct{})
	go func() {
		defer close(done)
		f()
	}()
	select {
	case <-done:
	case <-time.After(10 * time.Second):
		t.Fatal("test timed out — possible hang")
	}
}

// --- Integer edge cases ---

func TestPentestIntZero(t *testing.T) {
	stdout, _, _ := runScript(t, `test 0 -eq 0; echo $?`, "")
	assert.Equal(t, "0\n", stdout)
}

func TestPentestIntOne(t *testing.T) {
	stdout, _, _ := runScript(t, `test 1 -eq 1; echo $?`, "")
	assert.Equal(t, "0\n", stdout)
}

func TestPentestIntMaxInt32(t *testing.T) {
	max32 := strconv.Itoa(math.MaxInt32)
	stdout, _, _ := runScript(t, `test `+max32+` -eq `+max32+`; echo $?`, "")
	assert.Equal(t, "0\n", stdout)
}

func TestPentestIntMaxInt64(t *testing.T) {
	max64 := strconv.FormatInt(math.MaxInt64, 10)
	stdout, _, _ := runScript(t, `test `+max64+` -eq `+max64+`; echo $?`, "")
	assert.Equal(t, "0\n", stdout)
}

func TestPentestIntOverflowPositive(t *testing.T) {
	huge := "99999999999999999999"
	stdout, stderr, _ := runScript(t, `test `+huge+` -gt 0; echo $?`, "")
	assert.Equal(t, "2\n", stdout)
	assert.Contains(t, stderr, "integer expression expected")
}

func TestPentestIntOverflowNegative(t *testing.T) {
	huge := "-99999999999999999999"
	stdout, stderr, _ := runScript(t, `test `+huge+` -lt 0; echo $?`, "")
	assert.Equal(t, "2\n", stdout)
	assert.Contains(t, stderr, "integer expression expected")
}

func TestPentestIntEmptyString(t *testing.T) {
	_, stderr, code := runScript(t, `test "" -eq 0`, "")
	assert.Equal(t, 2, code)
	assert.Contains(t, stderr, "integer expression expected")
}

func TestPentestIntWhitespaceOnly(t *testing.T) {
	_, stderr, code := runScript(t, `test "   " -eq 0`, "")
	assert.Equal(t, 2, code)
	assert.Contains(t, stderr, "integer expression expected")
}

func TestPentestIntHexRejected(t *testing.T) {
	_, stderr, code := runScript(t, `test 0x10 -eq 16`, "")
	assert.Equal(t, 2, code)
	assert.Contains(t, stderr, "integer expression expected")
}

func TestPentestIntOctalNotInterpreted(t *testing.T) {
	stdout, _, _ := runScript(t, `test 010 -eq 10; echo $?`, "")
	assert.Equal(t, "0\n", stdout)
}

func TestPentestIntFloatRejected(t *testing.T) {
	_, stderr, code := runScript(t, `test 3.14 -eq 3`, "")
	assert.Equal(t, 2, code)
	assert.Contains(t, stderr, "integer expression expected")
}

// --- Special files ---

func TestPentestDevNull(t *testing.T) {
	mustNotHang(t, func() {
		_, _, code := runScript(t, `test -f `+os.DevNull, "", interp.AllowedPaths([]string{filepath.Dir(os.DevNull)}))
		assert.Equal(t, 1, code, "/dev/null is not a regular file")
	})
}

func TestPentestDevNullExists(t *testing.T) {
	mustNotHang(t, func() {
		_, _, code := runScript(t, `test -e `+os.DevNull, "", interp.AllowedPaths([]string{filepath.Dir(os.DevNull)}))
		assert.Equal(t, 0, code, "/dev/null should exist")
	})
}

// --- Path edge cases ---

func TestPentestNonexistentFile(t *testing.T) {
	dir := t.TempDir()
	_, _, code := cmdRun(t, `test -f /absolutely/nonexistent`, dir)
	assert.Equal(t, 1, code)
}

func TestPentestEmptyFilename(t *testing.T) {
	dir := t.TempDir()
	_, _, code := cmdRun(t, `test -f ""`, dir)
	assert.Equal(t, 1, code)
}

func TestPentestDirectoryAsFile(t *testing.T) {
	dir := t.TempDir()
	_, _, code := cmdRun(t, `test -f .`, dir)
	assert.Equal(t, 1, code)
}

func TestPentestPathTraversal(t *testing.T) {
	dir := t.TempDir()
	_, _, code := runScript(t, `test -e ../../../etc/passwd`, dir, interp.AllowedPaths([]string{dir}))
	assert.Equal(t, 1, code)
}

func TestPentestDoubleSlashes(t *testing.T) {
	dir := t.TempDir()
	writeFile(t, dir, "f.txt", "data")
	_, _, code := cmdRun(t, `test -f .//f.txt`, dir)
	assert.Equal(t, 0, code)
}

func TestPentestFlagLikeFilename(t *testing.T) {
	dir := t.TempDir()
	writeFile(t, dir, "-f", "data")
	_, _, code := cmdRun(t, `test -e -- -f`, dir)
	// 3 args: "-e", "--", "-f" — parsed as unary -e with operand "--",
	// then "-f" is unconsumed → exit 2 (too many arguments).
	assert.Equal(t, 2, code)
}

// --- Flag and argument injection ---

func TestPentestUnknownFlag(t *testing.T) {
	_, _, code := runScript(t, `test --no-such-flag`, "")
	assert.Equal(t, 0, code)
}

func TestPentestDoubleDash(t *testing.T) {
	_, _, code := runScript(t, `test --`, "")
	assert.Equal(t, 0, code)
}

func TestPentestMultipleStdinArgs(t *testing.T) {
	_, _, code := runScript(t, `test "-" = "-"`, "")
	assert.Equal(t, 0, code)
}

// --- Context timeout ---

func TestPentestTimeoutOnFile(t *testing.T) {
	dir := t.TempDir()
	writeFile(t, dir, "f.txt", "data")
	mustNotHang(t, func() {
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		_, _, _ = runScriptCtx(ctx, t, `test -f f.txt`, dir, interp.AllowedPaths([]string{dir}))
	})
}

// --- Bracket syntax edge cases ---

func TestPentestBracketEmptyBrackets(t *testing.T) {
	_, stderr, code := runScript(t, `[ ]`, "")
	assert.Equal(t, 1, code, "stderr: %s", stderr)
}

func TestPentestBracketOnlyClose(t *testing.T) {
	_, stderr, code := runScript(t, `[`, "")
	assert.Equal(t, 2, code)
	assert.Contains(t, stderr, "missing `]'")
}

// --- Large argument count ---

func TestPentestManyArguments(t *testing.T) {
	mustNotHang(t, func() {
		_, _, code := runScript(t, `test "a" -a "b" -a "c" -a "d" -a "e" -a "f"`, "")
		assert.Equal(t, 0, code)
	})
}

// --- Precedence / complex expressions ---

func TestPentestPrecedenceAndOr(t *testing.T) {
	stdout, _, _ := runScript(t, `test " " -o "" -a ""; echo $?`, "")
	assert.Equal(t, "0\n", stdout)
}

func TestPentestNestedParentheses(t *testing.T) {
	stdout, _, _ := runScript(t, `test '(' '(' "a" = "a" ')' ')'; echo $?`, "")
	assert.Equal(t, "0\n", stdout)
}

func TestPentestMissingCloseParen(t *testing.T) {
	_, stderr, code := runScript(t, `test '(' "a"`, "")
	assert.Equal(t, 2, code)
	assert.Contains(t, stderr, "missing ')'")
}

func TestPentestEmptyParens(t *testing.T) {
	_, _, code := runScript(t, `test '(' ')'`, "")
	assert.Equal(t, 2, code)
}

func TestPentestDeepNesting(t *testing.T) {
	// Build a deeply nested expression: ( ( ( ... "x" ... ) ) )
	var script string
	depth := 200
	for i := 0; i < depth; i++ {
		script += "'(' "
	}
	script += `"x"`
	for i := 0; i < depth; i++ {
		script += " ')'"
	}
	_, stderr, code := runScript(t, "test "+script, "")
	assert.Equal(t, 2, code)
	assert.Contains(t, stderr, "too deeply nested")
}

func TestPentestDeepNesting1000(t *testing.T) {
	// 1000+ levels of nesting should be rejected by depth limit, not stack overflow.
	var script string
	depth := 1000
	for i := 0; i < depth; i++ {
		script += "'(' "
	}
	script += `"x"`
	for i := 0; i < depth; i++ {
		script += " ')'"
	}
	mustNotHang(t, func() {
		_, stderr, code := runScript(t, "test "+script, "")
		assert.Equal(t, 2, code)
		assert.Contains(t, stderr, "too deeply nested")
	})
}

func TestPentestNullByteInFilePath(t *testing.T) {
	// Null bytes in file paths should not bypass sandbox checks.
	dir := t.TempDir()
	// test -f with a path containing a null byte
	_, _, code := cmdRun(t, "test -f \"file\x00/../etc/passwd\"", dir)
	assert.NotEqual(t, 0, code)
}

func TestPentestVeryLongString(t *testing.T) {
	// Very long string arguments should not cause panics or hangs.
	mustNotHang(t, func() {
		long := make([]byte, 100*1024)
		for i := range long {
			long[i] = 'a'
		}
		script := `test "` + string(long) + `" = "` + string(long) + `"; echo $?`
		stdout, _, code := runScript(t, script, "")
		assert.Equal(t, 0, code)
		assert.Equal(t, "0\n", stdout)
	})
}

func TestPentestVeryLongFilePath(t *testing.T) {
	// Very long file path should not cause panics.
	dir := t.TempDir()
	long := make([]byte, 100*1024)
	for i := range long {
		long[i] = 'a'
	}
	mustNotHang(t, func() {
		_, _, code := cmdRun(t, `test -f "`+string(long)+`"`, dir)
		assert.Equal(t, 1, code)
	})
}

func TestPentestBangAsStringOperand(t *testing.T) {
	// "!" should be treated as a literal string in binary expressions
	stdout, stderr, code := runScript(t, `test '!' = '!'; echo $?`, "")
	assert.Equal(t, 0, code)
	assert.Equal(t, "0\n", stdout)
	assert.Empty(t, stderr)
}

func TestPentestParenAsStringOperand(t *testing.T) {
	// "(" should be treated as a literal string in binary expressions
	stdout, stderr, code := runScript(t, `test '(' = '('; echo $?`, "")
	assert.Equal(t, 0, code)
	assert.Equal(t, "0\n", stdout)
	assert.Empty(t, stderr)
}
