package redact

import (
	"bufio"
	"bytes"
	"io"
	"strings"
	"testing"

	"github.com/replicatedhq/troubleshoot/pkg/constants"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// Test 1.2 & 1.3: NewLineReader creates instance correctly
func TestNewLineReader(t *testing.T) {
	input := strings.NewReader("test")
	lr := NewLineReader(input)

	require.NotNil(t, lr)
	require.NotNil(t, lr.reader)
}

// Test 1.8: Empty file → (nil, false, io.EOF)
func TestLineReader_EmptyFile(t *testing.T) {
	lr := NewLineReader(strings.NewReader(""))

	line, hadNewline, err := lr.ReadLine()

	assert.Nil(t, line)
	assert.False(t, hadNewline)
	assert.Equal(t, io.EOF, err)
}

// Test 1.9: Single line with \n → (content, true, nil)
func TestLineReader_SingleLineWithNewline(t *testing.T) {
	lr := NewLineReader(strings.NewReader("hello world\n"))

	line, hadNewline, err := lr.ReadLine()

	assert.Equal(t, []byte("hello world"), line)
	assert.True(t, hadNewline)
	assert.NoError(t, err)

	// Second read should return EOF
	line, hadNewline, err = lr.ReadLine()
	assert.Nil(t, line)
	assert.False(t, hadNewline)
	assert.Equal(t, io.EOF, err)
}

// Test 1.10: Single line without \n → (content, false, io.EOF)
func TestLineReader_SingleLineWithoutNewline(t *testing.T) {
	lr := NewLineReader(strings.NewReader("hello world"))

	line, hadNewline, err := lr.ReadLine()

	assert.Equal(t, []byte("hello world"), line)
	assert.False(t, hadNewline)
	assert.Equal(t, io.EOF, err)
}

// Test 1.11: Multiple lines with \n → correct for each
func TestLineReader_MultipleLinesWithNewlines(t *testing.T) {
	input := "line1\nline2\nline3\n"
	lr := NewLineReader(strings.NewReader(input))

	// First line
	line, hadNewline, err := lr.ReadLine()
	assert.Equal(t, []byte("line1"), line)
	assert.True(t, hadNewline)
	assert.NoError(t, err)

	// Second line
	line, hadNewline, err = lr.ReadLine()
	assert.Equal(t, []byte("line2"), line)
	assert.True(t, hadNewline)
	assert.NoError(t, err)

	// Third line
	line, hadNewline, err = lr.ReadLine()
	assert.Equal(t, []byte("line3"), line)
	assert.True(t, hadNewline)
	assert.NoError(t, err)

	// EOF
	line, hadNewline, err = lr.ReadLine()
	assert.Nil(t, line)
	assert.False(t, hadNewline)
	assert.Equal(t, io.EOF, err)
}

// Test 1.12: Last line without \n → (content, false, io.EOF)
func TestLineReader_LastLineWithoutNewline(t *testing.T) {
	input := "line1\nline2\nline3"
	lr := NewLineReader(strings.NewReader(input))

	// First line
	line, hadNewline, err := lr.ReadLine()
	assert.Equal(t, []byte("line1"), line)
	assert.True(t, hadNewline)
	assert.NoError(t, err)

	// Second line
	line, hadNewline, err = lr.ReadLine()
	assert.Equal(t, []byte("line2"), line)
	assert.True(t, hadNewline)
	assert.NoError(t, err)

	// Third line (no trailing newline)
	line, hadNewline, err = lr.ReadLine()
	assert.Equal(t, []byte("line3"), line)
	assert.False(t, hadNewline)
	assert.Equal(t, io.EOF, err)
}

// Test 1.13: Binary data (no \n) → (all content, false, io.EOF)
func TestLineReader_BinaryData(t *testing.T) {
	binaryData := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0xFF, 0xFE}
	lr := NewLineReader(bytes.NewReader(binaryData))

	line, hadNewline, err := lr.ReadLine()

	assert.Equal(t, binaryData, line)
	assert.False(t, hadNewline)
	assert.Equal(t, io.EOF, err)
}

// Test 1.14: Line exceeding max size → error
func TestLineReader_LineExceedingMaxSize(t *testing.T) {
	// Create a line that exceeds SCANNER_MAX_SIZE
	largeData := make([]byte, constants.SCANNER_MAX_SIZE+100)
	for i := range largeData {
		largeData[i] = 'a'
	}

	lr := NewLineReader(bytes.NewReader(largeData))

	line, hadNewline, err := lr.ReadLine()

	assert.Nil(t, line)
	assert.False(t, hadNewline)
	assert.Error(t, err)
	assert.ErrorIs(t, err, bufio.ErrTooLong)
}

// Test 1.15: File with only \n → ([], true, nil)
func TestLineReader_OnlyNewline(t *testing.T) {
	lr := NewLineReader(strings.NewReader("\n"))

	line, hadNewline, err := lr.ReadLine()

	assert.Equal(t, []byte{}, line) // Empty line
	assert.True(t, hadNewline)
	assert.NoError(t, err)

	// Second read should return EOF
	line, hadNewline, err = lr.ReadLine()
	assert.Nil(t, line)
	assert.False(t, hadNewline)
	assert.Equal(t, io.EOF, err)
}

// Additional test: File with empty lines (multiple newlines)
func TestLineReader_EmptyLines(t *testing.T) {
	input := "\n\n\n"
	lr := NewLineReader(strings.NewReader(input))

	// First empty line
	line, hadNewline, err := lr.ReadLine()
	assert.Equal(t, []byte{}, line)
	assert.True(t, hadNewline)
	assert.NoError(t, err)

	// Second empty line
	line, hadNewline, err = lr.ReadLine()
	assert.Equal(t, []byte{}, line)
	assert.True(t, hadNewline)
	assert.NoError(t, err)

	// Third empty line
	line, hadNewline, err = lr.ReadLine()
	assert.Equal(t, []byte{}, line)
	assert.True(t, hadNewline)
	assert.NoError(t, err)

	// EOF
	line, hadNewline, err = lr.ReadLine()
	assert.Nil(t, line)
	assert.False(t, hadNewline)
	assert.Equal(t, io.EOF, err)
}

// Additional test: Mixed content with and without newlines
func TestLineReader_MixedContent(t *testing.T) {
	input := "line1\n\nline3"
	lr := NewLineReader(strings.NewReader(input))

	// First line
	line, hadNewline, err := lr.ReadLine()
	assert.Equal(t, []byte("line1"), line)
	assert.True(t, hadNewline)
	assert.NoError(t, err)

	// Empty line
	line, hadNewline, err = lr.ReadLine()
	assert.Equal(t, []byte{}, line)
	assert.True(t, hadNewline)
	assert.NoError(t, err)

	// Last line without newline
	line, hadNewline, err = lr.ReadLine()
	assert.Equal(t, []byte("line3"), line)
	assert.False(t, hadNewline)
	assert.Equal(t, io.EOF, err)
}

// Additional test: Large but valid file (under max size)
func TestLineReader_LargeValidFile(t *testing.T) {
	// Create a line that's large but under the limit
	largeData := make([]byte, constants.SCANNER_MAX_SIZE-100)
	for i := range largeData {
		largeData[i] = 'x'
	}
	largeData = append(largeData, '\n')

	lr := NewLineReader(bytes.NewReader(largeData))

	line, hadNewline, err := lr.ReadLine()

	assert.Equal(t, constants.SCANNER_MAX_SIZE-100, len(line))
	assert.True(t, hadNewline)
	assert.NoError(t, err)
}

// Additional test: Binary file with embedded newlines
func TestLineReader_BinaryWithEmbeddedNewlines(t *testing.T) {
	binaryData := []byte{0x01, 0x02, '\n', 0x03, 0x04, '\n', 0x05}
	lr := NewLineReader(bytes.NewReader(binaryData))

	// First "line" (up to first \n)
	line, hadNewline, err := lr.ReadLine()
	assert.Equal(t, []byte{0x01, 0x02}, line)
	assert.True(t, hadNewline)
	assert.NoError(t, err)

	// Second "line"
	line, hadNewline, err = lr.ReadLine()
	assert.Equal(t, []byte{0x03, 0x04}, line)
	assert.True(t, hadNewline)
	assert.NoError(t, err)

	// Last "line" without newline
	line, hadNewline, err = lr.ReadLine()
	assert.Equal(t, []byte{0x05}, line)
	assert.False(t, hadNewline)
	assert.Equal(t, io.EOF, err)
}

// Test edge case: Very small reads
func TestLineReader_SingleByteReads(t *testing.T) {
	input := "a\nb\nc"
	lr := NewLineReader(strings.NewReader(input))

	line, hadNewline, err := lr.ReadLine()
	assert.Equal(t, []byte("a"), line)
	assert.True(t, hadNewline)
	assert.NoError(t, err)

	line, hadNewline, err = lr.ReadLine()
	assert.Equal(t, []byte("b"), line)
	assert.True(t, hadNewline)
	assert.NoError(t, err)

	line, hadNewline, err = lr.ReadLine()
	assert.Equal(t, []byte("c"), line)
	assert.False(t, hadNewline)
	assert.Equal(t, io.EOF, err)
}

// Benchmark: LineReader vs bufio.Scanner performance
func BenchmarkLineReader(b *testing.B) {
	// Create test data
	var buf bytes.Buffer
	for i := 0; i < 1000; i++ {
		buf.WriteString("This is line number ")
		buf.WriteString(string(rune(i)))
		buf.WriteString(" with some content\n")
	}
	data := buf.Bytes()

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		lr := NewLineReader(bytes.NewReader(data))
		for {
			_, _, err := lr.ReadLine()
			if err == io.EOF {
				break
			}
		}
	}
}
