package tui

import (
	"testing"
)

// goldenScreen is the exact sample screen from the spec.
const goldenScreen = `╭─── Claude Code v2.1.161 ─────────────────────────────────────────────────────────────────────────────────────────────╮
│                                                    │ Tips for getting started                                        │
│               Welcome back Enmanuel!               │ Run /init to create a CLAUDE.md file with instructions for Cla… │
│                                                    │ ─────────────────────────────────────────────────────────────── │
│                       ▐▛███▜▌                      │ What's new                                                      │
│                      ▝▜█████▛▘                     │ ` + "`OTEL_RESOURCE_ATTRIBUTES`" + ` values are now included as labels o… │
│                        ▘▘ ▝▝                       │ ` + "`claude agents`" + ` rows now show ` + "`done/total`" + ` before the detail w… │
│   Opus 4.8 (1M context) with xh… · Claude Max ·    │ ` + "`/mcp`" + ` now collapses claude.ai connectors you've never signed … │
│   gutierenmanuel15@gmail.com's Organization        │ /release-notes for more                                         │
│                   ~/fn_registry                    │                                                                 │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯


❯ responde unicamente con la palabra PONG, sin explicaciones

● PONG

✻ Crunched for 2s


────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
❯
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   Opus 4.8 (1M context) │ CTX: █░░░░░░░░░ 11% (107k/1.0M) │ IN:6k OUT:5 (cache:17k) │ ⎇ master [~4 ?28 ↑1] │ 22:26
   $0.565 │ +0/-0 │ Total: ↓107k/↑5 │ Limits: 5h:6% →02:40 │ 7d:11% →Sun 17:00 │ ⏱ 7s │ ~/fn_registry
  ← for agents`

func TestParseClaudeTUI(t *testing.T) {
	t.Run("golden screen — banner + status bar + single Q&A", func(t *testing.T) {
		got := ParseClaudeTUI(goldenScreen)

		if got.Answer != "PONG" {
			t.Errorf("Answer = %q, want %q", got.Answer, "PONG")
		}
		if len(got.Turns) != 2 {
			t.Errorf("len(Turns) = %d, want 2", len(got.Turns))
			for i, turn := range got.Turns {
				t.Logf("  Turns[%d]: role=%s text=%q", i, turn.Role, turn.Text)
			}
			return
		}
		if got.Turns[0].Role != ClaudeTurnUser {
			t.Errorf("Turns[0].Role = %q, want %q", got.Turns[0].Role, ClaudeTurnUser)
		}
		wantUserText := "responde unicamente con la palabra PONG, sin explicaciones"
		if got.Turns[0].Text != wantUserText {
			t.Errorf("Turns[0].Text = %q, want %q", got.Turns[0].Text, wantUserText)
		}
		if got.Turns[1].Role != ClaudeTurnAssistant {
			t.Errorf("Turns[1].Role = %q, want %q", got.Turns[1].Role, ClaudeTurnAssistant)
		}
		if got.Turns[1].Text != "PONG" {
			t.Errorf("Turns[1].Text = %q, want %q", got.Turns[1].Text, "PONG")
		}
	})

	t.Run("multiline assistant response", func(t *testing.T) {
		screen := `❯ explica brevemente

● linea uno
linea dos`
		got := ParseClaudeTUI(screen)
		if len(got.Turns) != 2 {
			t.Fatalf("len(Turns) = %d, want 2; turns: %+v", len(got.Turns), got.Turns)
		}
		wantText := "linea uno\nlinea dos"
		if got.Turns[1].Text != wantText {
			t.Errorf("Turns[1].Text = %q, want %q", got.Turns[1].Text, wantText)
		}
		if !contains(got.Answer, "linea uno") || !contains(got.Answer, "linea dos") {
			t.Errorf("Answer %q should contain both continuation lines", got.Answer)
		}
	})

	t.Run("tool_use + tool_result + final assistant text", func(t *testing.T) {
		screen := `❯ pregunta

● Read(main.go)

  ⎿  Read 50 lines

● aqui esta el resumen`
		got := ParseClaudeTUI(screen)

		if len(got.Turns) != 4 {
			t.Fatalf("len(Turns) = %d, want 4; turns: %+v", len(got.Turns), got.Turns)
		}
		if got.Turns[0].Role != ClaudeTurnUser {
			t.Errorf("Turns[0].Role = %q", got.Turns[0].Role)
		}
		if got.Turns[1].Role != ClaudeTurnToolUse {
			t.Errorf("Turns[1].Role = %q, want tool_use", got.Turns[1].Role)
		}
		if got.Turns[1].ToolName != "Read" {
			t.Errorf("Turns[1].ToolName = %q, want Read", got.Turns[1].ToolName)
		}
		if got.Turns[2].Role != ClaudeTurnToolResult {
			t.Errorf("Turns[2].Role = %q, want tool_result", got.Turns[2].Role)
		}
		if got.Turns[3].Role != ClaudeTurnAssistant {
			t.Errorf("Turns[3].Role = %q, want assistant", got.Turns[3].Role)
		}
		// Answer must be ONLY the assistant text, not the tool_use.
		if got.Answer != "aqui esta el resumen" {
			t.Errorf("Answer = %q, want %q", got.Answer, "aqui esta el resumen")
		}
	})

	t.Run("multi-turn — answer from last user only", func(t *testing.T) {
		screen := `❯ primera pregunta

● primera respuesta

❯ segunda pregunta

● segunda respuesta`
		got := ParseClaudeTUI(screen)
		if len(got.Turns) != 4 {
			t.Fatalf("len(Turns) = %d, want 4; turns: %+v", len(got.Turns), got.Turns)
		}
		if got.Answer != "segunda respuesta" {
			t.Errorf("Answer = %q, want %q", got.Answer, "segunda respuesta")
		}
	})

	t.Run("no banner no status bar — minimal screen", func(t *testing.T) {
		screen := "❯ hola\n\n● mundo"
		got := ParseClaudeTUI(screen)
		if len(got.Turns) != 2 {
			t.Fatalf("len(Turns) = %d, want 2; turns: %+v", len(got.Turns), got.Turns)
		}
		if got.Answer != "mundo" {
			t.Errorf("Answer = %q, want %q", got.Answer, "mundo")
		}
	})

	t.Run("determinism — same input produces same output", func(t *testing.T) {
		first := ParseClaudeTUI(goldenScreen)
		second := ParseClaudeTUI(goldenScreen)
		if first.Answer != second.Answer {
			t.Errorf("non-deterministic: %q != %q", first.Answer, second.Answer)
		}
		if len(first.Turns) != len(second.Turns) {
			t.Errorf("non-deterministic turns count: %d != %d", len(first.Turns), len(second.Turns))
		}
	})
}

// TestParseClaudeTUI_Spinner verifies that the generation spinner — which shows a
// DIFFERENT random gerund word on every frame ("Whatchamacalliting", "Forging",
// "Puzzling", "Crunched"...) — is never folded into the answer, regardless of the
// word, the glyph, or whether the "(Ns · tokens)" suffix is present yet.
func TestParseClaudeTUI_Spinner(t *testing.T) {
	cases := []struct {
		name   string
		screen string
		want   string
	}{
		{
			name:   "spinner with tokens suffix glued after answer",
			screen: "❯ di PONG\n\n● PONG\n\n✽ Whatchamacalliting… (2s · ↓ 1 tokens · esc to interrupt)\n",
			want:   "PONG",
		},
		{
			name:   "spinner early frame, no suffix yet, different word",
			screen: "❯ di HOLA\n\n● HOLA\n\n✶ Puzzling…\n",
			want:   "HOLA",
		},
		{
			name:   "classic crunched line",
			screen: "❯ x\n\n● respuesta\n\n✻ Crunched for 4s\n",
			want:   "respuesta",
		},
		{
			name:   "spinner BEFORE the answer block (mid-generation snapshot)",
			screen: "❯ pregunta\n\n✽ Forging… (1s · ↑ 3 tokens · esc to interrupt)\n\n● respuesta parcial\n",
			want:   "respuesta parcial",
		},
		{
			name:   "assistant line ending in ellipsis is NOT treated as spinner",
			screen: "❯ x\n\n● la historia continua…\n",
			want:   "la historia continua…",
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got := ParseClaudeTUI(tc.screen)
			if got.Answer != tc.want {
				t.Errorf("Answer = %q, want %q", got.Answer, tc.want)
			}
		})
	}
}

func contains(s, sub string) bool {
	return len(sub) == 0 || (len(s) >= len(sub) && (s == sub ||
		len(s) > 0 && containsStr(s, sub)))
}

func containsStr(s, sub string) bool {
	for i := 0; i <= len(s)-len(sub); i++ {
		if s[i:i+len(sub)] == sub {
			return true
		}
	}
	return false
}
