// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cgroup_test

import (
	"internal/runtime/cgroup"
	"strings"
	"testing"
)

type nextLine struct {
	line       string
	incomplete bool // next call before this line should return incomplete
}

func complete(s string) nextLine {
	return nextLine{line: s}
}
func incomplete(s string) nextLine {
	return nextLine{line: s, incomplete: true}
}

const scratchSize = 8

var readerTests = []struct {
	name     string
	contents string
	want     []nextLine
}{
	{
		name:     "empty",
		contents: "",
	},
	{
		name:     "single",
		contents: "1234\n",
		want: []nextLine{
			complete("1234"),
		},
	},
	{
		name:     "single-incomplete",
		contents: "1234",
		want: []nextLine{
			incomplete("1234"),
		},
	},
	{
		name:     "single-exact",
		contents: "1234567\n",
		want: []nextLine{
			complete("1234567"),
		},
	},
	{
		name:     "single-exact-incomplete",
		contents: "12345678",
		want: []nextLine{
			incomplete("12345678"),
		},
	},
	{
		name: "multi",
		contents: `1234
5678
`,
		want: []nextLine{
			complete("1234"),
			complete("5678"),
		},
	},
	{
		name: "multi-short",
		contents: `12
34
56
78
`,
		want: []nextLine{
			complete("12"),
			complete("34"),
			complete("56"),
			complete("78"),
		},
	},
	{
		name: "multi-notrailingnewline",
		contents: `1234
5678`,
		want: []nextLine{
			complete("1234"),
			incomplete("5678"),
		},
	},
	{
		name: "middle-too-long",
		contents: `1234
1234567890
5678
`,
		want: []nextLine{
			complete("1234"),
			incomplete("12345678"),
			complete("5678"),
		},
	},
	{
		// Multiple reads required to find newline.
		name: "middle-way-too-long",
		contents: `1234
12345678900000000000000000000000000000000000000000000000000
5678
`,
		want: []nextLine{
			complete("1234"),
			incomplete("12345678"),
			complete("5678"),
		},
	},
}

func TestLineReader(t *testing.T) {
	for _, tc := range readerTests {
		t.Run(tc.name, func(t *testing.T) {
			var scratch [scratchSize]byte
			l := cgroup.NewLineReader(0, scratch[:], readString(tc.contents))

			var got []nextLine
			for {
				err := l.Next()
				if err == cgroup.ErrEOF {
					break
				} else if err == cgroup.ErrIncompleteLine {
					got = append(got, incomplete(string(l.Line())))
				} else if err != nil {
					t.Fatalf("next got err %v", err)
				} else {
					got = append(got, complete(string(l.Line())))
				}
			}

			if len(got) != len(tc.want) {
				t.Logf("got lines %+v", got)
				t.Logf("want lines %+v", tc.want)
				t.Fatalf("lineReader got %d lines, want %d", len(got), len(tc.want))
			}

			for i := range got {
				if got[i].line != tc.want[i].line {
					t.Errorf("line %d got %q want %q", i, got[i].line, tc.want[i].line)
				}
				if got[i].incomplete != tc.want[i].incomplete {
					t.Errorf("line %d got incomplete %v want %v", i, got[i].incomplete, tc.want[i].incomplete)
				}
			}
		})
	}
}

func FuzzLineReader(f *testing.F) {
	for _, tc := range readerTests {
		f.Add(tc.contents)
	}
	f.Fuzz(func(t *testing.T, input string) {
		scratch := make([]byte, scratchSize)
		reader := cgroup.NewLineReader(0, scratch, readString(input))
		for expected := range strings.Lines(input) {
			err := reader.Next()
			line := reader.Line()

			var expectedErr error
			if len(expected) > scratchSize {
				expected = expected[:scratchSize]
				expectedErr = cgroup.ErrIncompleteLine
			} else if expected[len(expected)-1] == '\n' {
				expected = expected[:len(expected)-1]
			} else {
				expectedErr = cgroup.ErrIncompleteLine
			}

			if err != expectedErr {
				t.Fatalf("got err %v, want %v", err, expectedErr)
			}

			if string(line) != expected {
				t.Fatalf("got %q, want %q", string(line), expected)
			}
		}
		err := reader.Next()
		if err != cgroup.ErrEOF {
			t.Fatalf("got %v, want EOF", err)
		}
	})
}
