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

import (
	"context"
	"math"
	"strconv"
	"strings"
	"testing"
	"time"

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

// --- Integer edge cases ---

func TestPentestIntZero(t *testing.T) {
	stdout, _, code := cmdRun(t, `printf "%d\n" 0`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "0\n", stdout)
}

func TestPentestIntOne(t *testing.T) {
	stdout, _, code := cmdRun(t, `printf "%d\n" 1`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "1\n", stdout)
}

func TestPentestIntMaxInt32(t *testing.T) {
	stdout, _, code := cmdRun(t, `printf "%d\n" 2147483647`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "2147483647\n", stdout)
}

func TestPentestIntMaxInt64(t *testing.T) {
	max := strconv.FormatInt(math.MaxInt64, 10)
	stdout, _, code := cmdRun(t, `printf "%d\n" `+max)
	assert.Equal(t, 0, code)
	assert.Equal(t, max+"\n", stdout)
}

func TestPentestIntMaxInt64PlusOne(t *testing.T) {
	// MaxInt64 + 1 = 9223372036854775808 — overflow clamps to MaxInt64, exit 0 (bash compat)
	stdout, stderr, code := cmdRun(t, `printf "%d\n" 9223372036854775808`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "9223372036854775807\n", stdout)
	assert.Contains(t, stderr, "Numerical result out of range")
}

func TestPentestIntHugeNumber(t *testing.T) {
	stdout, stderr, code := cmdRun(t, `printf "%d\n" 99999999999999999999`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "9223372036854775807\n", stdout)
	assert.Contains(t, stderr, "Numerical result out of range")
}

func TestPentestIntNegativeOne(t *testing.T) {
	stdout, _, code := cmdRun(t, `printf "%d\n" -1`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "-1\n", stdout)
}

func TestPentestIntNegativeHuge(t *testing.T) {
	stdout, stderr, code := cmdRun(t, `printf "%d\n" -9999999999999999999`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "-9223372036854775808\n", stdout)
	assert.Contains(t, stderr, "Numerical result out of range")
}

func TestPentestIntPlusZero(t *testing.T) {
	stdout, _, code := cmdRun(t, `printf "%d\n" +0`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "0\n", stdout)
}

func TestPentestIntPlusOne(t *testing.T) {
	stdout, _, code := cmdRun(t, `printf "%d\n" +1`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "1\n", stdout)
}

func TestPentestIntEmpty(t *testing.T) {
	// Empty string for %d → default 0
	stdout, _, code := cmdRun(t, `printf "%d\n" ""`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "0\n", stdout)
}

func TestPentestIntWhitespace(t *testing.T) {
	// Whitespace-only string for %d → invalid
	stdout, stderr, code := cmdRun(t, `printf "%d\n" "   "`)
	assert.Equal(t, 1, code)
	assert.Equal(t, "0\n", stdout)
	assert.Contains(t, stderr, "printf:")
}

// --- Same for bytes (%u, %o, %x) ---

func TestPentestUnsignedMaxInt64(t *testing.T) {
	max := strconv.FormatInt(math.MaxInt64, 10)
	stdout, _, code := cmdRun(t, `printf "%u\n" `+max)
	assert.Equal(t, 0, code)
	assert.Equal(t, max+"\n", stdout)
}

func TestPentestHexMaxInt32(t *testing.T) {
	stdout, _, code := cmdRun(t, `printf "%x\n" 2147483647`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "7fffffff\n", stdout)
}

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

func TestPentestUnknownFlags(t *testing.T) {
	// Unknown single-dash flag is rejected with exit 2 (bash compat)
	_, stderr, code := cmdRun(t, `printf -f "%s" hello`)
	assert.Equal(t, 2, code)
	assert.Contains(t, stderr, "invalid option")
}

func TestPentestFollowFlag(t *testing.T) {
	// Unknown long flag is rejected with exit 2 (bash compat)
	_, stderr, code := cmdRun(t, `printf --follow "%s" hello`)
	assert.Equal(t, 2, code)
	assert.Contains(t, stderr, "invalid option")
}

func TestPentestEndOfFlagsWithFlagLikeFilename(t *testing.T) {
	// After --, "-v" is treated as the format string (no specifiers),
	// and "hello" is an unused extra argument. Output is just "-v".
	stdout, _, code := cmdRun(t, `printf -- "-v" hello`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "-v", stdout)
}

func TestPentestEndOfFlagsWithPercentS(t *testing.T) {
	// After --, "%s" is the format string and "hello" is the argument.
	stdout, _, code := cmdRun(t, `printf -- "%s" hello`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "hello", stdout)
}

func TestPentestMultipleStdinDash(t *testing.T) {
	// printf doesn't read stdin, so "-" is just a string
	stdout, _, code := cmdRun(t, `printf "%s %s\n" - -`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "- -\n", stdout)
}

// --- Format reuse bounding ---

func TestPentestFormatReuseMany(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	// 100 args should be fine
	args := strings.Repeat("x ", 100)
	stdout, _, code := runScriptCtx(ctx, t, `printf "%s\n" `+args, "")
	assert.Equal(t, 0, code)
	lines := strings.Split(strings.TrimRight(stdout, "\n"), "\n")
	assert.Equal(t, 100, len(lines))
}

func TestPentestNoSpecifiersExtraArgs(t *testing.T) {
	// Format with no specifiers and extra args — format is printed once
	stdout, _, code := cmdRun(t, `printf "hello\n" a b c d e`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "hello\n", stdout)
}

// --- Width/precision bounds ---

func TestPentestHugeWidth(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	stdout, _, code := runScriptCtx(ctx, t, `printf "%99999d\n" 42`, "")
	assert.Equal(t, 0, code)
	// Width should be clamped to 10000
	assert.LessOrEqual(t, len(stdout), 10002)
	assert.Contains(t, stdout, "42")
}

func TestPentestHugePrecision(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	stdout, _, code := runScriptCtx(ctx, t, `printf "%.99999f\n" 3.14`, "")
	assert.Equal(t, 0, code)
	// Precision should be clamped to 10000
	assert.LessOrEqual(t, len(stdout), 10010)
}

// --- Rejected specifiers ---

func TestPentestPercentN(t *testing.T) {
	_, stderr, code := cmdRun(t, `printf "%n" foo`)
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "printf:")
	assert.Contains(t, stderr, "not supported")
}

func TestPentestPercentQ(t *testing.T) {
	_, stderr, code := cmdRun(t, `printf "%q" foo`)
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "printf:")
}

func TestPentestPercentA(t *testing.T) {
	_, stderr, code := cmdRun(t, `printf "%a" 3.14`)
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "printf:")
}

func TestPentestPercentAUpper(t *testing.T) {
	_, stderr, code := cmdRun(t, `printf "%A" 3.14`)
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "printf:")
}

// --- V flag rejection ---

func TestPentestVFlag(t *testing.T) {
	_, stderr, code := cmdRun(t, `printf -v myvar "%s" hello`)
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "printf:")
}

// --- Special characters in format and args ---

func TestPentestNulByteInArg(t *testing.T) {
	// Args containing special characters should be handled safely
	stdout, _, code := cmdRun(t, `printf "%s\n" hello`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "hello\n", stdout)
}

func TestPentestEmptyArgs(t *testing.T) {
	stdout, _, code := cmdRun(t, `printf "%s|%s|%s\n" "" "" ""`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "||\n", stdout)
}

// --- Float edge cases ---

func TestPentestFloatInfinity(t *testing.T) {
	stdout, _, code := cmdRun(t, `printf "%f\n" inf`)
	assert.Equal(t, 0, code)
	assert.Contains(t, stdout, "inf")
}

func TestPentestFloatNaN(t *testing.T) {
	stdout, _, code := cmdRun(t, `printf "%f\n" nan`)
	assert.Equal(t, 0, code)
	assert.Contains(t, stdout, "nan")
}

func TestPentestFloatZero(t *testing.T) {
	stdout, _, code := cmdRun(t, `printf "%f\n" 0`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "0.000000\n", stdout)
}

// --- Behavior matching ---

func TestPentestBashCompatPercentD(t *testing.T) {
	// Bash: printf "%d\n" 42 → "42\n"
	stdout, _, code := cmdRun(t, `printf "%d\n" 42`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "42\n", stdout)
}

func TestPentestBashCompatFormatReusePartial(t *testing.T) {
	// Bash: printf "%s=%d\n" a 1 b → "a=1\nb=0\n"
	stdout, _, code := cmdRun(t, `printf "%s=%d\n" a 1 b`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "a=1\nb=0\n", stdout)
}

// --- Star width/precision ---

func TestPentestStarWidth(t *testing.T) {
	// printf "%*s\n" 10 hello → right-aligned in 10-char field
	stdout, _, code := cmdRun(t, `printf "%*s\n" 10 hello`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "     hello\n", stdout)
}

func TestPentestStarPrecision(t *testing.T) {
	// printf "%.*f\n" 2 3.14159 → "3.14\n"
	stdout, _, code := cmdRun(t, `printf "%.*f\n" 2 3.14159`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "3.14\n", stdout)
}

func TestPentestStarWidthInvalid(t *testing.T) {
	// Invalid number for * width → exit code 1
	stdout, stderr, code := cmdRun(t, `printf "%*d\n" abc 42`)
	assert.Equal(t, 1, code)
	assert.Equal(t, "42\n", stdout)
	assert.Contains(t, stderr, "printf:")
}

func TestPentestStarPrecisionInvalid(t *testing.T) {
	// Invalid number for * precision → exit code 1
	_, stderr, code := cmdRun(t, `printf "%.*f\n" abc 3.14`)
	assert.Equal(t, 1, code)
	assert.Contains(t, stderr, "printf:")
}

func TestPentestStarWidthNegative(t *testing.T) {
	// Negative width via * → left-align (bash behavior)
	stdout, _, code := cmdRun(t, `printf "%*s|\n" -10 hi`)
	assert.Equal(t, 0, code)
	assert.Equal(t, "hi        |\n", stdout)
}

func TestPentestBashCompatInvalidNumContinues(t *testing.T) {
	// Bash prints 0 and continues with exit code 1
	stdout, stderr, code := cmdRun(t, `printf "%d %d\n" abc 42`)
	assert.Equal(t, 1, code)
	assert.Equal(t, "0 42\n", stdout)
	assert.Contains(t, stderr, "printf:")
}
