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

import (
	"context"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

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

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

// --- Integer edge cases ---

func TestUniqPentestSkipFieldsZero(t *testing.T) {
	dir := t.TempDir()
	writeFile(t, dir, "in.txt", "a\na\n")
	stdout, _, code := cmdRun(t, "uniq -f 0 in.txt", dir)
	assert.Equal(t, 0, code)
	assert.Equal(t, "a\n", stdout)
}

func TestUniqPentestSkipFieldsOne(t *testing.T) {
	dir := t.TempDir()
	writeFile(t, dir, "in.txt", "x a\ny a\n")
	stdout, _, code := cmdRun(t, "uniq -f 1 in.txt", dir)
	assert.Equal(t, 0, code)
	assert.Equal(t, "x a\n", stdout)
}

func TestUniqPentestSkipFieldsMaxInt(t *testing.T) {
	dir := t.TempDir()
	writeFile(t, dir, "in.txt", "a\nb\n")
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	stdout, _, code := runScriptCtx(ctx, t, "uniq -f 2147483647 in.txt", dir, interp.AllowedPaths([]string{dir}))
	assert.Equal(t, 0, code)
	assert.Equal(t, "a\n", stdout)
}

func TestUniqPentestSkipFieldsOverflow(t *testing.T) {
	dir := t.TempDir()
	writeFile(t, dir, "in.txt", "a\nb\n")
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	stdout, _, code := runScriptCtx(ctx, t, "uniq -f 99999999999999999999 in.txt", dir, interp.AllowedPaths([]string{dir}))
	assert.Equal(t, 0, code)
	assert.Equal(t, "a\n", stdout)
}

func TestUniqPentestSkipCharsZero(t *testing.T) {
	dir := t.TempDir()
	writeFile(t, dir, "in.txt", "abc\nabcd\n")
	stdout, _, code := cmdRun(t, "uniq -s 0 in.txt", dir)
	assert.Equal(t, 0, code)
	assert.Equal(t, "abc\nabcd\n", stdout)
}

func TestUniqPentestCheckCharsZero(t *testing.T) {
	dir := t.TempDir()
	writeFile(t, dir, "in.txt", "abc\ndef\n")
	stdout, _, code := cmdRun(t, "uniq -w 0 in.txt", dir)
	assert.Equal(t, 0, code)
	assert.Equal(t, "abc\n", stdout)
}

func TestUniqPentestCheckCharsMaxInt(t *testing.T) {
	dir := t.TempDir()
	writeFile(t, dir, "in.txt", "a\na\n")
	stdout, _, code := cmdRun(t, "uniq -w 2147483647 in.txt", dir)
	assert.Equal(t, 0, code)
	assert.Equal(t, "a\n", stdout)
}

func TestUniqPentestCheckCharsOverflow(t *testing.T) {
	dir := t.TempDir()
	writeFile(t, dir, "in.txt", "a\na\n\x08")
	stdout, _, code := cmdRun(t, "uniq -d -u -w340282366920938463463374607431768211456 in.txt", dir)
	assert.Equal(t, 0, code)
	assert.Equal(t, "", stdout)
}

// --- Long lines ---

func TestUniqPentestLineBelowCap(t *testing.T) {
	dir := t.TempDir()
	line := strings.Repeat("a", 1<<20-2)
	content := line + "\n" + line + "\n"
	require.NoError(t, os.WriteFile(filepath.Join(dir, "in.txt"), []byte(content), 0644))
	stdout, _, code := cmdRun(t, "uniq in.txt", dir)
	assert.Equal(t, 0, code)
	assert.Equal(t, line+"\n", stdout)
}

func TestUniqPentestLineAtCap(t *testing.T) {
	dir := t.TempDir()
	line := strings.Repeat("a", 1<<20)
	content := line + "\n"
	require.NoError(t, os.WriteFile(filepath.Join(dir, "in.txt"), []byte(content), 0644))
	_, _, code := cmdRun(t, "uniq in.txt", dir)
	assert.Equal(t, 1, code)
}

func TestUniqPentestLineBeyondCap(t *testing.T) {
	dir := t.TempDir()
	line := strings.Repeat("a", 1<<20+1)
	require.NoError(t, os.WriteFile(filepath.Join(dir, "in.txt"), []byte(line), 0644))
	_, _, code := cmdRun(t, "uniq in.txt", dir)
	assert.Equal(t, 1, code)
}

// --- Path and filename edge cases ---

func TestUniqPentestNonExistentFile(t *testing.T) {
	dir := t.TempDir()
	_, stderr, code := cmdRun(t, "uniq nonexistent.txt", dir)
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "uniq:")
}

func TestUniqPentestEmptyFilename(t *testing.T) {
	dir := t.TempDir()
	_, stderr, code := cmdRun(t, `uniq ""`, dir)
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "uniq:")
}

func TestUniqPentestDoubleDashFlagLikeFilename(t *testing.T) {
	dir := t.TempDir()
	writeFile(t, dir, "-c", "content\n")
	stdout, _, code := cmdRun(t, "uniq -- -c", dir)
	assert.Equal(t, 0, code)
	assert.Equal(t, "content\n", stdout)
}

func TestUniqPentestOutsideAllowedPaths(t *testing.T) {
	allowed := t.TempDir()
	secret := t.TempDir()
	require.NoError(t, os.WriteFile(filepath.Join(secret, "secret.txt"), []byte("secret"), 0644))

	secretPath := strings.ReplaceAll(filepath.Join(secret, "secret.txt"), `\`, `/`)
	_, stderr, code := runScript(t, "uniq "+secretPath, allowed, interp.AllowedPaths([]string{allowed}))
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "uniq:")
}

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

func TestUniqPentestUnknownFlagLong(t *testing.T) {
	dir := t.TempDir()
	_, stderr, code := cmdRun(t, "uniq --follow in.txt", dir)
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "uniq:")
}

func TestUniqPentestUnknownFlagShort(t *testing.T) {
	dir := t.TempDir()
	_, stderr, code := cmdRun(t, "uniq -x in.txt", dir)
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "uniq:")
}

func TestUniqPentestMultipleStdinDash(t *testing.T) {
	dir := t.TempDir()
	writeFile(t, dir, "src.txt", "a\na\n")
	_, stderr, code := cmdRun(t, "uniq - - < src.txt", dir)
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "extra operand")
}

// --- Context cancellation ---

func TestUniqPentestPreCancelledContext(t *testing.T) {
	dir := t.TempDir()
	writeFile(t, dir, "in.txt", "a\na\nb\n")

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	done := make(chan struct{})
	go func() {
		runScriptCtx(ctx, t, "uniq in.txt", dir, interp.AllowedPaths([]string{dir}))
		close(done)
	}()
	select {
	case <-done:
	case <-time.After(5 * time.Second):
		t.Fatal("uniq with pre-cancelled context did not return within 5s")
	}
}

// --- Large input ---

func TestUniqPentestManyLines(t *testing.T) {
	dir := t.TempDir()
	var sb strings.Builder
	for i := 0; i < 10000; i++ {
		sb.WriteString("line\n")
	}
	writeFile(t, dir, "in.txt", sb.String())
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	stdout, _, code := runScriptCtx(ctx, t, "uniq in.txt", dir, interp.AllowedPaths([]string{dir}))
	assert.Equal(t, 0, code)
	assert.Equal(t, "line\n", stdout)
}

func TestUniqPentestManyUniqueLines(t *testing.T) {
	dir := t.TempDir()
	var sb strings.Builder
	for i := 0; i < 10000; i++ {
		sb.WriteString("line")
		sb.WriteString(strings.Repeat("x", i%100))
		sb.WriteByte('\n')
	}
	content := sb.String()
	writeFile(t, dir, "in.txt", content)
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	_, _, code := runScriptCtx(ctx, t, "uniq -c in.txt", dir, interp.AllowedPaths([]string{dir}))
	assert.Equal(t, 0, code)
}

// --- Behavior matching: -D with no duplicates ---

func TestUniqPentestAllRepeatedNoDuplicates(t *testing.T) {
	dir := t.TempDir()
	writeFile(t, dir, "in.txt", "a\nb\nc\n")
	stdout, _, code := cmdRun(t, "uniq -D in.txt", dir)
	assert.Equal(t, 0, code)
	assert.Equal(t, "", stdout)
}

// --- Null byte handling ---

func TestUniqPentestBinaryContent(t *testing.T) {
	dir := t.TempDir()
	content := []byte{0xfc, 0x80, 0x80, '\n', 0xfc, 0x80, 0x80, '\n'}
	require.NoError(t, os.WriteFile(filepath.Join(dir, "bad.bin"), content, 0644))
	stdout, _, code := cmdRun(t, "uniq bad.bin", dir)
	assert.Equal(t, 0, code)
	assert.Equal(t, "\xfc\x80\x80\n", stdout)
}

// --- GTFOBins validation ---

// TestUniqGTFOBinsFileReadSandboxEscape verifies that the GTFOBins file-read
// technique for uniq is blocked by the AllowedPaths sandbox.
//
// GTFOBins: https://gtfobins.org/gtfobins/uniq/
// Technique: uniq /path/to/file
func TestUniqGTFOBinsFileReadSandboxEscape(t *testing.T) {
	allowed := t.TempDir()
	secret := t.TempDir()
	require.NoError(t, os.WriteFile(filepath.Join(secret, "secret.txt"), []byte("secret data\n"), 0644))
	secretPath := strings.ReplaceAll(filepath.Join(secret, "secret.txt"), `\`, `/`)
	_, stderr, code := runScript(t, "uniq "+secretPath, allowed, interp.AllowedPaths([]string{allowed}))
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "uniq:")
}
