package cli

import (
	"strings"
	"testing"

	"iop/packages/go/config"
)

func TestClaudeTUIOutputFilterExtractsAssistantMessageOnly(t *testing.T) {
	filter := newClaudeTUIOutputFilter("")

	got := filter.Filter("\x1b[2C\r\x1b[7A\u25cf\x1b[1CIOP_MESSAGE_ONLY.\r\x1b[2B\u00b7 Cogitating... (2s · 1 tokens)\x1b[K\r\n\u276f ")
	if got != "IOP_MESSAGE_ONLY." {
		t.Fatalf("Filter() = %q, want %q", got, "IOP_MESSAGE_ONLY.")
	}
	if got := filter.Flush(); got != "" {
		t.Fatalf("Flush() should not duplicate already-emitted output, got %q", got)
	}
	if strings.Contains(got, "\x1b") || strings.Contains(got, "Cogitating") || strings.Contains(got, "\u276f") {
		t.Fatalf("filtered message still contains terminal chrome: %q", got)
	}
}

func TestClaudeTUIOutputFilterSuppressesRepaintedMessage(t *testing.T) {
	filter := newClaudeTUIOutputFilter("")

	if got := filter.Filter("\u25cf IOP_MESSAGE_ONCE.\r\n"); got != "IOP_MESSAGE_ONCE." {
		t.Fatalf("Filter() = %q, want first message", got)
	}
	if got := filter.Filter("\r\x1b[4A\u25cf IOP_MESSAGE_ONCE.\r\n"); got != "" {
		t.Fatalf("repainted Filter() = %q, want deferred output", got)
	}
	if got := filter.Flush(); got != "" {
		t.Fatalf("Flush() should not duplicate already-emitted output, got %q", got)
	}
}

func TestClaudeTUIOutputFilterSuppressesPreviousAssistantWhilePromptEchoes(t *testing.T) {
	filter := newClaudeTUIOutputFilter("second prompt", "OLD_REPLY")

	for _, chunk := range []string{
		"\u25cf OLD_REPLY\r\n\u276f second",
		" prompt\r\n",
	} {
		if got := filter.Filter(chunk); got != "" {
			t.Fatalf("Filter(%q) = %q, want previous reply suppressed", chunk, got)
		}
	}

	got := filter.Filter("\u25cf NEW_REPLY\r\n\u276f ")
	if got != "NEW_REPLY" {
		t.Fatalf("Filter() = %q, want new assistant reply", got)
	}
}

func TestClaudeTUIOutputFilterAllowsSameTextAfterCurrentPrompt(t *testing.T) {
	filter := newClaudeTUIOutputFilter("repeat prompt", "SAME_REPLY")

	got := filter.Filter("\u25cf SAME_REPLY\r\n\u276f repeat prompt\r\n\u25cf SAME_REPLY\r\n\u276f ")
	if got != "SAME_REPLY" {
		t.Fatalf("Filter() = %q, want repeated reply after current prompt", got)
	}
}

func TestClaudeTUIOutputFilterUsesLatestAssistantMessage(t *testing.T) {
	filter := newClaudeTUIOutputFilter("")

	filter.Filter("\u25cf IOP_FIRST_MESSAGE.\r\n\u2500\r\n")
	got := filter.Filter("\u25cf IOP_SECOND_MESSAGE.\r\n\u276f ")

	if got != "\nIOP_SECOND_MESSAGE." {
		t.Fatalf("Filter() = %q, want latest message replacement", got)
	}
}

func TestPassthroughOutputFilterKeepsNonClaudeTerminalOutput(t *testing.T) {
	filter := newPersistentOutputFilter("raw-terminal", testProfile("sh", true), "", "")

	got := filter.Filter("\x1b[31mreply:hello\x1b[0m")

	if got != "\x1b[31mreply:hello\x1b[0m" {
		t.Fatalf("Filter() = %q", got)
	}
}

func TestTerminalPassthroughOutputFilterSuppressesPromptEcho(t *testing.T) {
	filter := newPersistentOutputFilter("raw-terminal", testProfile("sh", true), "hello", "")

	if got := filter.Filter("he"); got != "" {
		t.Fatalf("first prompt echo Filter() = %q, want empty", got)
	}
	if filter.HasOutput() {
		t.Fatalf("HasOutput() = true while only prompt echo was seen")
	}
	if got := filter.Filter("llo\r\n"); got != "" {
		t.Fatalf("completed prompt echo Filter() = %q, want empty", got)
	}
	if filter.HasOutput() {
		t.Fatalf("HasOutput() = true after only prompt echo was seen")
	}

	got := filter.Filter("reply:hello\r\n")
	if got != "reply:hello\r\n" {
		t.Fatalf("reply Filter() = %q, want reply", got)
	}
	if !filter.HasOutput() {
		t.Fatalf("HasOutput() = false after reply")
	}
}

func TestTerminalPassthroughOutputFilterSuppressesPromptEchoBeforeReplyInSameChunk(t *testing.T) {
	filter := newPersistentOutputFilter("raw-terminal", testProfile("sh", true), "hello", "")

	got := filter.Filter("hello\r\nreply:hello\r\n")
	if got != "reply:hello\r\n" {
		t.Fatalf("Filter() = %q, want reply without echoed prompt", got)
	}
}

func TestClaudeTUIOutputFilterSuppressesStartupNoiseBeforeAssistantMarker(t *testing.T) {
	filter := newClaudeTUIOutputFilter("hello")

	filter.Filter("\r\x1b[2Khello\r\n")
	for _, chunk := range []string{
		"\r\x1b[2Ksud\uc5d0 \ub300\ud574\r\n",
		"\u2722 Dilly-dallying\u2026\r\n",
		"* Dilly-dallying\u2026\r\n",
		"\r\x1b[2K\uc124\uba85\ud574\ubd10\r\n",
	} {
		if got := filter.Filter(chunk); got != "" {
			t.Fatalf("Filter(%q) = %q, want suppressed startup noise", chunk, got)
		}
	}

	if got := filter.Flush(); got != "" {
		t.Fatalf("Flush() = %q, want no startup noise", got)
	}
}

func TestClaudeTUIOutputFilterKeepsMultilineAssistantMessage(t *testing.T) {
	filter := newClaudeTUIOutputFilter("")

	got := filter.Filter("\u25cf line one\r\nline two\r\n\r\nline four.\r\n\r\n\u273b Crunched for 4s\r\n\u23f5\u23f5 bypass permissions on (shift+tab to cycle) \u00b7 \u2190 for agents\r\n\u276f ")

	want := "line one\nline two\n\nline four."
	if got != want {
		t.Fatalf("Filter() = %q, want %q", got, want)
	}
}

func TestClaudeTUIOutputFilterDoesNotFallbackBeforeAssistantMarker(t *testing.T) {
	filter := newClaudeTUIOutputFilter("hello")

	filter.Filter("hello\r\n")
	if got := filter.Filter("real reply.\r\n"); got != "" {
		t.Fatalf("Filter() = %q, want markerless output suppressed", got)
	}
	filter.Filter("\u23f5\u23f5 bypass permissions on (shift+tab to cycle) \u00b7 \u2190 for agents\r\n")

	if got := filter.Flush(); got != "" {
		t.Fatalf("Flush() = %q, want markerless output suppressed", got)
	}
}

func TestClaudeTUIOutputFilterKeepsAssistantAfterStartupNoise(t *testing.T) {
	filter := newClaudeTUIOutputFilter("sudo\uc5d0 \ub300\ud574 \uc124\uba85\ud574\ubd10")

	for _, chunk := range []string{
		"\r\x1b[2Ksud\uc5d0 \ub300\ud574\r\n",
		"\u2722 Dilly-dallying\u2026\r\n",
		"* Dilly-dallying\u2026\r\n",
		"\r\x1b[2K\uc124\uba85\ud574\ubd10\r\n",
	} {
		if got := filter.Filter(chunk); got != "" {
			t.Fatalf("startup Filter(%q) = %q, want suppressed", chunk, got)
		}
	}

	got := filter.Filter("\u25cf sudo is a command for elevated privileges\r\n\u2722 Dilly-dallying\u2026\r\n\u276f ")
	if got != "sudo is a command for elevated privileges" {
		t.Fatalf("Filter() = %q, want assistant message only", got)
	}
}

func TestClaudeTUIOutputFilterWaitsForLineBoundary(t *testing.T) {
	filter := newClaudeTUIOutputFilter("")

	if got := filter.Filter("\u25cf sudo runs commands"); got != "" {
		t.Fatalf("first Filter() = %q", got)
	}
	got := filter.Filter("\r\n\u276f ")
	if got != "sudo runs commands" {
		t.Fatalf("second Filter() = %q, want completed line", got)
	}
}

func TestClaudeTUIOutputFilterEmitsSuffixAfterLineRepaint(t *testing.T) {
	filter := newClaudeTUIOutputFilter("")

	if got := filter.Filter("\u25cf First sentence.\r\n\u276f "); got != "First sentence." {
		t.Fatalf("first Filter() = %q", got)
	}
	got := filter.Filter("\r\x1b[2A\x1b[2K\u25cf First sentence. Second sentence.\r\n\u276f ")
	if got != " Second sentence." {
		t.Fatalf("second Filter() = %q, want suffix only", got)
	}
}

func TestClaudeTUIOutputFilterEmitsCompleteLineBeforePartialNextLine(t *testing.T) {
	filter := newClaudeTUIOutputFilter("")

	got := filter.Filter("\u25cf line one\r\nline two")
	if got != "line one\n" {
		t.Fatalf("Filter() = %q, want first completed line", got)
	}

	got = filter.Filter("\r\n\u276f ")
	if got != "line two" {
		t.Fatalf("second Filter() = %q, want completed second line", got)
	}
}

func TestPreferLongerClaudeMessageUsesScreenRepaintTail(t *testing.T) {
	got, closed := preferLongerClaudeMessage("first chunk", true, "first chunk\nsecond chunk", true)

	if got != "first chunk\nsecond chunk" {
		t.Fatalf("message = %q, want longer screen repaint candidate", got)
	}
	if !closed {
		t.Fatalf("closed = false, want true")
	}
}

func TestClaudeTUIOutputFilterDropsTransientStatusWithoutAssistantMarker(t *testing.T) {
	filter := newClaudeTUIOutputFilter("\uc548\ub155?")

	filter.Filter("\uc548\ub155?\r\n")
	filter.Filter("3\r\n\r\n\r\nThundering\u20266\r\n")

	if got := filter.Flush(); got != "" {
		t.Fatalf("Flush() = %q, want no message", got)
	}
}

func TestClaudeTUIOutputFilterKeepsAssistantAfterTransientStatus(t *testing.T) {
	filter := newClaudeTUIOutputFilter("\uc548\ub155?")

	filter.Filter("\uc548\ub155?\r\n")
	filter.Filter("3\r\n\r\nThundering\u20266\r\n")
	got := filter.Filter("\u25cf \uc548\ub155\ud558\uc138\uc694.\r\n")

	if got != "\uc548\ub155\ud558\uc138\uc694." {
		t.Fatalf("Filter() = %q, want %q", got, "\uc548\ub155\ud558\uc138\uc694.")
	}
}

func TestClaudeTUIOutputFilterIgnoresEffortStatusChrome(t *testing.T) {
	filter := newClaudeTUIOutputFilter("hello")

	filter.Filter("\x1b[2C\u25cf high\u00b7/effort\r\n")
	filter.Filter("in\u2026\r\n\r\n\r\n\r\n\r\ng\r\n\u276f ")

	if got := filter.Flush(); got != "" {
		t.Fatalf("Flush() = %q, want no chrome output", got)
	}
	if filter.HasOutput() {
		t.Fatalf("HasOutput() = true, want false for chrome-only screen")
	}
}

func TestClaudeTUIOutputFilterAcceptsRecordCircleAssistantMarker(t *testing.T) {
	filter := newClaudeTUIOutputFilter("hello")

	got := filter.Filter("\u23faIOP_TUI_MARKER_OK\r\n\u2733 Doodling\u2026 (1s \u00b7 \u2193 1 tokens)\r\n\u276f ")
	if got != "IOP_TUI_MARKER_OK" {
		t.Fatalf("Filter() = %q, want %q", got, "IOP_TUI_MARKER_OK")
	}
}

func TestClaudeTUIOutputFilterSuppressesPromptEchoFragments(t *testing.T) {
	filter := newClaudeTUIOutputFilter("Reply with exactly and only this token: IOP_TUI_MARKER_OK")

	for _, chunk := range []string{
		"\u25cf R\r\n",
		"\u25cf eplywithex\r\n",
		"\u25cf actly\r\n",
		"\u25cf token:\r\n",
	} {
		if got := filter.Filter(chunk); got != "" {
			t.Fatalf("Filter(%q) = %q, want prompt echo suppressed", chunk, got)
		}
	}

	got := filter.Filter("\u23faIOP_TUI_MARKER_OK\r\n\u276f ")
	if got != "IOP_TUI_MARKER_OK" {
		t.Fatalf("Filter() = %q, want assistant token", got)
	}
}

func TestClaudeStartupPromptDetectionIgnoresCursorPositioning(t *testing.T) {
	rawTrust := "\x1b[2GQuick\x1b[8Gsafety\x1b[15Gcheck:\x1b[22GIs\x1b[25Gthis\x1b[30Ga\x1b[32Gproject\r\n" +
		"\x1b[2G\x1b[38;5;153m❯\x1b[4G\x1b[38;5;246m1.\x1b[7G\x1b[38;5;153mYes,\x1b[12GI\x1b[14Gtrust\x1b[20Gthis\x1b[25Gfolder\x1b[39m\r\n"
	if !shouldAcceptClaudeWorkspaceTrust(rawTrust) {
		t.Fatalf("workspace trust prompt with cursor positioning was not detected")
	}

	rawBypass := "\x1b[2GWARNING:\x1b[11GClaude\x1b[18GCode\x1b[23Grunning\x1b[31Gin\x1b[34GBypassPermissions\x1b[52Gmode\r\n" +
		"\x1b[4G2.\x1b[7GYes,\x1b[12GI\x1b[14Gaccept\r\n"
	if !shouldAcceptClaudeBypassWarning(rawBypass) {
		t.Fatalf("bypass warning with cursor positioning was not detected")
	}
}

func TestClaudeTerminalCancelMessageDetectsSessionLimit(t *testing.T) {
	raw := "\x1b[2G⎿ \u00a0You've hit your session limit · resets 12:50pm (Asia/Seoul)\r\n" +
		"\x1b[2GWhat do you want to do?\r\n" +
		"\x1b[2G❯\x1b[4G1.\x1b[7GStop\x1b[12Gand\x1b[16Gwait\x1b[21Gfor\x1b[25Glimit\x1b[31Gto\x1b[34Greset\r\n" +
		"\x1b[4G2.\x1b[7GUpgrade\x1b[15Gyour\x1b[20Gplan\r\n"
	msg, ok := claudeTerminalCancelMessage(raw)
	if !ok {
		t.Fatalf("session limit cancellation was not detected")
	}
	if !strings.Contains(msg, "session limit") {
		t.Fatalf("cancel message = %q, want session limit detail", msg)
	}
}

func testProfile(command string, terminal bool) config.CLIProfileConf {
	return config.CLIProfileConf{Command: command, Terminal: terminal}
}
