// 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.

// Exploratory pentest for the tr builtin.
//
// These tests probe edge cases in flag/argument handling, special input
// patterns, memory safety boundaries, and behaviour-matching with GNU tr.

package tr_test

import (
	"context"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"

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

const pentestTimeout = 10 * time.Second

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(pentestTimeout):
		t.Fatalf("operation did not complete within %s", pentestTimeout)
	}
}

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

func TestTrPentestUnknownLongFlag(t *testing.T) {
	_, stderr, code := runScript(t, "tr --no-such-flag a b", t.TempDir())
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "tr:")
}

func TestTrPentestDoubleDashOperands(t *testing.T) {
	stdout, _, code := trRun(t, "aha", "-- ah -H")
	assert.Equal(t, 0, code)
	assert.Equal(t, "-H-", stdout)
}

func TestTrPentestBasicTranslation(t *testing.T) {
	stdout, _, code := trRun(t, "abc", "a X")
	assert.Equal(t, 0, code)
	assert.Equal(t, "Xbc", stdout)
}

// --- Empty/whitespace operands ---

func TestTrPentestEmptySet1(t *testing.T) {
	dir := t.TempDir()
	writeFile(t, dir, "in.txt", "abc")
	_, _, code := runScript(t, `cat in.txt | tr '' '[.*]'`, dir, interp.AllowedPaths([]string{dir}))
	assert.Equal(t, 0, code)
}

func TestTrPentestEmptySet2WithTruncate(t *testing.T) {
	dir := t.TempDir()
	writeFile(t, dir, "in.txt", "abc")
	_, _, code := runScript(t, `cat in.txt | tr -t abc ''`, dir, interp.AllowedPaths([]string{dir}))
	assert.Equal(t, 0, code)
}

// --- Context timeout on infinite source ---

func TestTrPentestContextTimeout(t *testing.T) {
	mustNotHang(t, func() {
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()
		_, _, _ = runScriptCtx(ctx, t, "tr a b", t.TempDir())
	})
}

// --- Large repetitive input ---

func TestTrPentestLargeInput(t *testing.T) {
	dir := t.TempDir()
	large := strings.Repeat("a", 100000)
	writeFile(t, dir, "in.txt", large)
	stdout, _, code := runScript(t, "cat in.txt | tr a b", dir, interp.AllowedPaths([]string{dir}))
	assert.Equal(t, 0, code)
	expected := strings.Repeat("b", 100000)
	assert.Equal(t, expected, stdout)
}

// --- Repeat construct edge cases ---

func TestTrPentestRepeatFillEmptyCount(t *testing.T) {
	stdout, _, code := trRun(t, "abcdef", "abcdef '1[x*]2'")
	assert.Equal(t, 0, code)
	assert.Equal(t, "1xxxx2", stdout)
}

func TestTrPentestRepeatOctal(t *testing.T) {
	stdout, _, code := trRun(t, "abc", "abc '[b*3]'")
	assert.Equal(t, 0, code)
	assert.Equal(t, "bbb", stdout)
}

func TestTrPentestInvalidRepeatCount(t *testing.T) {
	dir := t.TempDir()
	writeFile(t, dir, "in.txt", "")
	_, stderr, code := runScript(t, "cat in.txt | tr 'a' '[b*c]'", dir, interp.AllowedPaths([]string{dir}))
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "invalid repeat count")
}

// --- Character class edge cases ---

func TestTrPentestInvalidClassName(t *testing.T) {
	_, stderr, code := trRun(t, "", "-d '[:bogus:]'")
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "invalid character class")
}

func TestTrPentestDeleteAlnumDotsRemain(t *testing.T) {
	stdout, _, code := trRun(t, ".abc123.", "-d '[:alnum:]'")
	assert.Equal(t, 0, code)
	assert.Equal(t, "..", stdout)
}

// --- Backslash escape edge cases ---

func TestTrPentestOctalThreeDigit(t *testing.T) {
	stdout, _, code := trRun(t, "XYZ", `XYZ '\0\11\77'`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "\000\t?", stdout)
}

func TestTrPentestUnrecognizedEscape(t *testing.T) {
	stdout, _, code := trRun(t, "q", `q '\q'`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "q", stdout)
}

// --- Combined flags ---

func TestTrPentestDeleteSqueezeCombined(t *testing.T) {
	stdout, _, code := trRun(t, "a.b.c $$$$code\\", `-ds a-z '$.'`)
	assert.Equal(t, 0, code)
	assert.Equal(t, ". $\\", stdout)
}

func TestTrPentestComplementSqueeze(t *testing.T) {
	stdout, _, code := trRun(t, "aaBBcDcc", "-sc a-z")
	assert.Equal(t, 0, code)
	assert.Equal(t, "aaBcDcc", stdout)
}

// --- Equivalence class edge cases ---

func TestTrPentestMultiCharEquiv(t *testing.T) {
	_, stderr, code := trRun(t, "", "-d '[=aa=]'")
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "equivalence class operand must be a single character")
}

// --- Pipe integration ---

func TestTrPentestPipeChain(t *testing.T) {
	dir := t.TempDir()
	writeFile(t, dir, "in.txt", "Hello World 123")
	stdout, _, code := runScript(t, "cat in.txt | tr A-Z a-z | tr -d '[:digit:]'", dir, interp.AllowedPaths([]string{dir}))
	assert.Equal(t, 0, code)
	assert.Equal(t, "hello world ", stdout)
}

// --- Complement with delete ---

func TestTrPentestComplementDeleteKeepAlpha(t *testing.T) {
	stdout, _, code := trRun(t, "abc123!@#", "-dc '[:alpha:]'")
	assert.Equal(t, 0, code)
	assert.Equal(t, "abc", stdout)
}

// --- Big C flag ---

func TestTrPentestBigCFlag(t *testing.T) {
	stdout, _, code := trRun(t, "Phone: 01234 567890", "-d -C 0-9")
	assert.Equal(t, 0, code)
	assert.Equal(t, "01234567890", stdout)
}
