package jobspec

import (
	"strings"
	"testing"
)

// FuzzParseMarkdownRoundTrip is the REQ-067 fuzz harness for R-015
// byte-exact body preservation. It generates random frontmatter + body
// combinations, runs ParseMarkdown, and asserts that the parsed Body
// equals the original body byte-for-byte whenever parsing succeeds.
// When parsing fails (bad frontmatter), the iteration passes — the
// parser is allowed to reject malformed input.
//
// The seed corpus (added via f.Add) covers adversarial fixtures: CRLF
// body, BOM prefix, no frontmatter, only-closing-separator, body with
// `---` inside a code fence, trailing whitespace, empty body. The seed
// corpus runs as regular tests under `go test` (CI); random input runs
// only under `go test -fuzz=FuzzParseMarkdownRoundTrip` in a dedicated
// process.
func FuzzParseMarkdownRoundTrip(f *testing.F) {
	// Seed 1: valid frontmatter + simple body.
	f.Add([]byte("---\nkind: Job\nname: seed1\n---\n# body\n"))

	// Seed 2: CRLF body.
	f.Add([]byte("---\r\nkind: Job\r\nname: seed2\r\n---\r\n# body\r\nCRLF\r\n"))

	// Seed 3: BOM prefix.
	f.Add([]byte("\uFEFF---\nkind: Job\nname: seed3\n---\nbody\n"))

	// Seed 4: no frontmatter (just body) — should fail to parse.
	f.Add([]byte("# just a body\nno frontmatter\n"))

	// Seed 5: frontmatter with only the closing `---` (no opening).
	f.Add([]byte("body\n---\nmore body\n"))

	// Seed 6: body containing `---` in a code fence.
	f.Add([]byte("---\nkind: Job\nname: seed6\n---\n```bash\necho '---'\n```\n"))

	// Seed 7: body with trailing whitespace.
	f.Add([]byte("---\nkind: Job\nname: seed7\n---\nbody with trailing spaces   \n"))

	// Seed 8: empty body.
	f.Add([]byte("---\nkind: Job\nname: seed8\n---\n"))

	// Seed 9: empty frontmatter (should fail).
	f.Add([]byte("---\n---\nbody\n"))

	// Seed 10: body with no trailing newline.
	f.Add([]byte("---\nkind: Job\nname: seed10\n---\nno trailing newline"))

	f.Fuzz(func(t *testing.T, data []byte) {
		// Reconstruct the body from the input so we can assert
		// byte-exact round-trip. We do this by re-splitting the
		// frontmatter using the same logic the parser uses, but only
		// to extract the expected body. If the input has no valid
		// frontmatter delimiter pair, ParseMarkdown will return an
		// error and we pass the iteration.
		expectedBody := extractExpectedBody(string(data))

		spec, err := ParseMarkdown(data)
		if err != nil {
			// Parser rejected the input — acceptable for a fuzz
			// iteration (the input may be malformed). Pass.
			return
		}
		// R-015: body must be byte-exact.
		if spec.Body != expectedBody {
			t.Errorf("R-015 body round-trip mismatch:\n got = %q\nwant = %q", spec.Body, expectedBody)
		}
	})
}

// extractExpectedBody returns the body portion of a Markdown jobspec
// input using the same delimiter-splitting logic as splitFrontmatter,
// so the fuzz harness can assert byte-exact preservation independently
// of the parser's internal extraction. If the input has no valid
// frontmatter, the result is "" (and ParseMarkdown will error).
func extractExpectedBody(content string) string {
	stripped := content
	if strings.HasPrefix(stripped, "\uFEFF") {
		stripped = stripped[len("\uFEFF"):]
	}
	trimmed := strings.TrimLeft(stripped, "\r\n\t ")
	if !strings.HasPrefix(trimmed, "---") {
		return ""
	}
	rest := trimmed[3:]
	if len(rest) > 0 && rest[0] != '\n' && rest[0] != '\r' {
		return ""
	}
	rest = strings.TrimLeft(rest, "\r\n")
	idx := findClosingDelimiter(rest)
	if idx < 0 {
		return ""
	}
	afterClose := rest[idx:]
	newlineIdx := strings.IndexAny(afterClose, "\r\n")
	if newlineIdx < 0 {
		return ""
	}
	bodyStart := newlineIdx
	if strings.HasPrefix(afterClose[bodyStart:], "\r\n") {
		bodyStart += 2
	} else {
		bodyStart += 1
	}
	return afterClose[bodyStart:]
}
