package agent

import (
	"strings"
	"testing"

	"reasonix/internal/event"
	"reasonix/internal/provider"
)

// TestTextSinkReproducesInlineOutput pins the byte-exact rendering of a turn's
// event stream with no markdown renderer (the piped / chat-bridge path). This
// is the same output the agent used to print inline before the event refactor.
func TestTextSinkReproducesInlineOutput(t *testing.T) {
	var b strings.Builder
	s := NewTextSink(&b, nil, 80)

	s.Emit(event.Event{Kind: event.TurnStarted})
	s.Emit(event.Event{Kind: event.Reasoning, Text: "let me think"})
	s.Emit(event.Event{Kind: event.Text, Text: "Hello"})
	s.Emit(event.Event{Kind: event.Message, Text: "Hello", Reasoning: "let me think"})
	s.Emit(event.Event{Kind: event.Usage, Usage: &provider.Usage{
		PromptTokens: 1000, CompletionTokens: 200, TotalTokens: 1200,
		CacheHitTokens: 900, CacheMissTokens: 100,
	}})
	s.Emit(event.Event{Kind: event.ToolDispatch, Tool: event.Tool{Name: "read_file", Args: `{"path":"a"}`}})
	s.Emit(event.Event{Kind: event.ToolResult, Tool: event.Tool{Name: "read_file", Output: "contents"}})
	s.Emit(event.Event{Kind: event.ToolResult, Tool: event.Tool{Name: "bash", Err: "blocked by permission policy"}})
	s.Emit(event.Event{Kind: event.Notice, Level: event.LevelInfo, Text: "tool output truncated: 5 of 100 bytes elided"})
	s.Emit(event.Event{Kind: event.Notice, Level: event.LevelWarn, Text: "response truncated: hit max output tokens"})

	want := "\x1b[2m  ▎ thinking\x1b[0m\n" + // reasoning header
		"\x1b[2mlet me think\x1b[0m" + // reasoning delta
		"\n" + // reasoning→answer separator
		"Hello" + // answer delta
		"\n" + // Message close (no renderer)
		"  · 1200 tok · in 1000 (900 cached / 100 new) · out 200\n" + // usage
		"  -> read_file {\"path\":\"a\"}\n" + // tool dispatch
		// successful read_file result is silent
		"  ⊘ bash blocked by permission policy\n" + // blocked result
		"  · tool output truncated: 5 of 100 bytes elided\n" + // info notice
		"  ! response truncated: hit max output tokens\n" // warn notice

	if got := b.String(); got != want {
		t.Errorf("TextSink output mismatch:\n got: %q\nwant: %q", got, want)
	}
}

// fakeRenderer returns a fixed string so the redraw escape sequence is testable
// without a real markdown library.
type fakeRenderer struct{}

func (fakeRenderer) Render(string) string { return "RENDERED" }

// TestTextSinkRedrawsWithRenderer pins the cursor-control redraw: with a
// renderer wired in, a completed text stream is moved over and replaced by the
// styled output. "ab\ncd" occupies one extra row (one newline), so the cursor
// moves up 1 and clears to end of screen.
func TestTextSinkRedrawsWithRenderer(t *testing.T) {
	var b strings.Builder
	s := NewTextSink(&b, fakeRenderer{}, 80)

	s.Emit(event.Event{Kind: event.TurnStarted})
	s.Emit(event.Event{Kind: event.Text, Text: "ab\ncd"})
	s.Emit(event.Event{Kind: event.Message, Text: "ab\ncd"})

	want := "ab\ncd" + "\r\033[1A\033[0J" + "RENDERED"
	if got := b.String(); got != want {
		t.Errorf("redraw output mismatch:\n got: %q\nwant: %q", got, want)
	}
}
