package status

import (
	"context"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

func TestClaudeCheckerRequestsStatus(t *testing.T) {
	dir := t.TempDir()
	fakeClaude := filepath.Join(dir, "claude")

	script := `#!/usr/bin/env sh
# Wait for startup readiness
printf 'Claude Code v2.1.142\n'
printf 'Try /help\n'
sleep 0.1

# Read command from stdin
IFS= read -r command

# If not /status (strip Ctrl+S prefix \x15 if present)
cmd="${command#?}"
if [ "$cmd" != "/status" ] && [ "$command" != "/status" ]; then
  printf 'unexpected command: %%s\n' "$command"
  exit 3
fi

# Print Claude /status output
printf 'Current session:         [████████░░░░░░░░░░░░] 2%% used\n'
printf 'Resets 10am (Asia/Seoul)\n'
printf '\n'
printf 'Current week (all models): [███████████████░░░░░] 50%% used\n'
printf 'Resets May 16, 6pm (Asia/Seoul)\n'

# Read exit command
IFS= read -r exit_command

# Cleanup
pkill -P $$ 2>/dev/null || true
exit 0
`

	if err := os.WriteFile(fakeClaude, []byte(script), 0o755); err != nil {
		t.Fatalf("write fake claude: %v", err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	checker := NewClaudeChecker(fakeClaude)
	status, err := checker.Check(ctx)
	if err != nil {
		t.Fatalf("Check failed: %v", err)
	}
	if status.DailyLimit != "98%" {
		t.Fatalf("DailyLimit: got %q want %q", status.DailyLimit, "98%")
	}
	if status.DailyResetTime != "10am (Asia/Seoul)" {
		t.Fatalf("DailyResetTime: got %q want %q", status.DailyResetTime, "10am (Asia/Seoul)")
	}
	if status.WeeklyLimit != "50%" {
		t.Fatalf("WeeklyLimit: got %q want %q", status.WeeklyLimit, "50%")
	}
	if status.WeeklyResetTime != "May 16, 6pm (Asia/Seoul)" {
		t.Fatalf("WeeklyResetTime: got %q want %q", status.WeeklyResetTime, "May 16, 6pm (Asia/Seoul)")
	}
}

func TestClaudeCheckerWaitsForDelayedUsageWeek(t *testing.T) {
	dir := t.TempDir()
	fakeClaude := filepath.Join(dir, "claude")

	script := `#!/usr/bin/env sh
printf 'Claude Code v2.1.142\n'
printf 'Try /help\n'
sleep 0.1

IFS= read -r command
cmd="${command#?}"
if [ "$cmd" != "/status" ] && [ "$command" != "/status" ]; then
  printf 'unexpected command: %%s\n' "$command"
  exit 3
fi

# Print Current session first, then sleep before week output
printf 'Current session:         [████████░░░░░░░░░░░░] 2%% used\n'
printf 'Resets 10am (Asia/Seoul)\n'
sleep 1

# Print Current week after delay
printf '\n'
printf 'Current week (all models): [███████████████░░░░░] 50%% used\n'
printf 'Resets May 16, 6pm (Asia/Seoul)\n'

IFS= read -r exit_command
pkill -P $$ 2>/dev/null || true
exit 0
`
	if err := os.WriteFile(fakeClaude, []byte(script), 0o755); err != nil {
		t.Fatalf("write fake claude: %v", err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	checker := NewClaudeChecker(fakeClaude)
	status, err := checker.Check(ctx)
	if err != nil {
		t.Fatalf("Check failed: %v", err)
	}
	if status.DailyLimit != "98%" {
		t.Fatalf("DailyLimit: got %q want %q", status.DailyLimit, "98%")
	}
	if status.DailyResetTime != "10am (Asia/Seoul)" {
		t.Fatalf("DailyResetTime: got %q want %q", status.DailyResetTime, "10am (Asia/Seoul)")
	}
	if status.WeeklyLimit != "50%" {
		t.Fatalf("WeeklyLimit: got %q want %q", status.WeeklyLimit, "50%")
	}
	if status.WeeklyResetTime != "May 16, 6pm (Asia/Seoul)" {
		t.Fatalf("WeeklyResetTime: got %q want %q", status.WeeklyResetTime, "May 16, 6pm (Asia/Seoul)")
	}
}

func TestClaudeCheckerReturnsRawFallbackWhenUsageHasNoLimitBlock(t *testing.T) {
	dir := t.TempDir()
	fakeClaude := filepath.Join(dir, "claude")

	script := `#!/usr/bin/env sh
printf 'Claude Code v2.1.142\n'
printf '? for shortcuts\n'
sleep 0.1

IFS= read -r command
cmd="${command#?}"
if [ "$cmd" != "/status" ] && [ "$command" != "/status" ]; then
  printf 'unexpected command: %%s\n' "$command"
  exit 3
fi

printf 'Last 24h · these are independent characteristics of your usage, not a breakdown\n'
printf 'Nothing over 10%% in this period — try the other window.\n'
printf 'd to day · w to week\n'
printf 'Esc to cancel\n'
exit 0
`
	if err := os.WriteFile(fakeClaude, []byte(script), 0o755); err != nil {
		t.Fatalf("write fake claude: %v", err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	checker := NewClaudeChecker(fakeClaude)
	status, err := checker.Check(ctx)
	if err != nil {
		t.Fatalf("Check failed: %v", err)
	}
	if status.DailyLimit != "" || status.WeeklyLimit != "" {
		t.Fatalf("expected no parsed limits, got daily=%q weekly=%q", status.DailyLimit, status.WeeklyLimit)
	}
	if !strings.Contains(status.RawOutput, "Nothing over 10%") {
		t.Fatalf("expected raw fallback output, got %q", status.RawOutput)
	}
}

func TestClaudeCheckerSelectsUsageAndParsesRepaintedScreen(t *testing.T) {
	dir := t.TempDir()
	fakeClaude := filepath.Join(dir, "claude")

	// Mimics the real TUI: startup banner, then /status panel that first paints
	// a loading state ("Scanning local sessions...", "Esc to cancel") via cursor
	// repaints, then later renders the actual Current session / Current week
	// content. The parser must wait through the loading state rather than
	// timing out on the partial repaint.
	script := `#!/usr/bin/env sh
printf 'Claude Code v2.1.142\n'
printf '? for shortcuts\n'
sleep 0.1

IFS= read -r command
cmd="${command#?}"
if [ "$cmd" != "/status" ] && [ "$command" != "/status" ]; then
  printf 'unexpected command: %%s\n' "$command"
  exit 3
fi

# Loading state with cursor repositioning escapes and "Esc to cancel" tail.
printf '\033[2J\033[H'
printf '  Usage\n'
printf '  Scanning local sessions...\n'
printf '  Refreshing...\n'
printf '  Esc to cancel\n'
sleep 1

# Repaint with real data after data fetch.
printf '\033[2J\033[H'
printf '  Settings  Status  Config   Usage   Stats\n'
printf '\n'
printf '  Session\n'
printf '\n'
printf '  Current session\n'
printf '  \033[48;5;102m\033[38;5;153m##                                                  \033[49m\033[39m 4%% used\n'
printf '  Resets 10am (Asia/Seoul)\n'
printf '\n'
printf '  Current week (all models)\n'
printf '                                                       0%% used\n'
printf '  Resets May 16, 6pm (Asia/Seoul)\n'
printf '\n'
printf '  Esc to cancel\n'

IFS= read -r exit_command
pkill -P $$ 2>/dev/null || true
exit 0
`
	if err := os.WriteFile(fakeClaude, []byte(script), 0o755); err != nil {
		t.Fatalf("write fake claude: %v", err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	checker := NewClaudeChecker(fakeClaude)
	status, err := checker.Check(ctx)
	if err != nil {
		t.Fatalf("Check failed: %v", err)
	}
	if status.DailyLimit != "96%" {
		t.Fatalf("DailyLimit: got %q want %q", status.DailyLimit, "96%")
	}
	if status.DailyResetTime != "10am (Asia/Seoul)" {
		t.Fatalf("DailyResetTime: got %q want %q", status.DailyResetTime, "10am (Asia/Seoul)")
	}
	if status.WeeklyLimit != "100%" {
		t.Fatalf("WeeklyLimit: got %q want %q", status.WeeklyLimit, "100%")
	}
	if status.WeeklyResetTime != "May 16, 6pm (Asia/Seoul)" {
		t.Fatalf("WeeklyResetTime: got %q want %q", status.WeeklyResetTime, "May 16, 6pm (Asia/Seoul)")
	}
	if status.Metadata["daily_label"] != "Current session" {
		t.Fatalf("daily_label: got %q want %q", status.Metadata["daily_label"], "Current session")
	}
	if status.Metadata["weekly_label"] != "Current week" {
		t.Fatalf("weekly_label: got %q want %q", status.Metadata["weekly_label"], "Current week")
	}
}

func TestClaudeCheckerParsesCursorRepaintedUsageScreen(t *testing.T) {
	dir := t.TempDir()
	fakeClaude := filepath.Join(dir, "claude")

	// Fake TUI that paints the /status panel via cursor-addressed writes,
	// matching how real Claude renders. We first write misleading payload
	// text ("21% of your usage" / "Resets garbage time") at rows 6/7, then
	// reposition the cursor and use EL (erase line) to overwrite those rows
	// with the actual data. In the raw stream both the garbage and the real
	// payload remain interleaved; only a terminal screen model that applies
	// the cursor moves and erases sees the final visible payload.
	//
	// Implementations that parse the line-oriented cleanANSI output without
	// rendering the screen will either pick up "21%" / "garbage time" as the
	// session payload or fail to find a coherent section at all, so they will
	// not satisfy the final assertions.
	script := `#!/usr/bin/env bash
printf 'Claude Code v2.1.143\n'
printf '? for shortcuts\n'
sleep 0.1

IFS= read -r command
cmd="${command#?}"
if [ "$cmd" != "/status" ] && [ "$command" != "/status" ]; then
  printf 'unexpected command: %s\n' "$command"
  exit 3
fi

ESC=$'\x1b'

# Clear screen, paint loading state with misleading numbers.
printf '%s[2J%s[H' "$ESC" "$ESC"
printf '%s[5;1H  Current session' "$ESC"
printf '%s[6;1H  21%% of your usage in last 24h' "$ESC"
printf '%s[7;1H  Resets garbage time' "$ESC"
printf '%s[9;1H  Current week (all models)' "$ESC"
printf '%s[10;1H  47%% of week things here' "$ESC"
printf '%s[11;1H  Resets garbage week time' "$ESC"
printf '%s[13;1H  Last 24h' "$ESC"
printf '%s[14;1H  d to day · w to week' "$ESC"
printf '%s[15;1H  Extra usage' "$ESC"
printf '%s[16;1H  Esc to cancel' "$ESC"

sleep 0.3

# Repaint with real payload by erasing and rewriting the same rows.
printf '%s[6;1H%s[2K  ####                                                  4%% used' "$ESC" "$ESC"
printf '%s[7;1H%s[2K  Resets 10am (Asia/Seoul)' "$ESC" "$ESC"
printf '%s[10;1H%s[2K                                                        0%% used' "$ESC" "$ESC"
printf '%s[11;1H%s[2K  Resets May 16, 6pm (Asia/Seoul)' "$ESC" "$ESC"

# Idle until the checker tears down the PTY.
sleep 30
exit 0
`
	if err := os.WriteFile(fakeClaude, []byte(script), 0o755); err != nil {
		t.Fatalf("write fake claude: %v", err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	checker := NewClaudeChecker(fakeClaude)
	status, err := checker.Check(ctx)
	if err != nil {
		t.Fatalf("Check failed: %v", err)
	}
	if status.DailyLimit != "96%" {
		t.Fatalf("DailyLimit: got %q want %q", status.DailyLimit, "96%")
	}
	if status.DailyResetTime != "10am (Asia/Seoul)" {
		t.Fatalf("DailyResetTime: got %q want %q", status.DailyResetTime, "10am (Asia/Seoul)")
	}
	if status.WeeklyLimit != "100%" {
		t.Fatalf("WeeklyLimit: got %q want %q", status.WeeklyLimit, "100%")
	}
	if status.WeeklyResetTime != "May 16, 6pm (Asia/Seoul)" {
		t.Fatalf("WeeklyResetTime: got %q want %q", status.WeeklyResetTime, "May 16, 6pm (Asia/Seoul)")
	}
	if status.Metadata["daily_label"] != "Current session" {
		t.Fatalf("daily_label: got %q want %q", status.Metadata["daily_label"], "Current session")
	}
	if status.Metadata["weekly_label"] != "Current week" {
		t.Fatalf("weekly_label: got %q want %q", status.Metadata["weekly_label"], "Current week")
	}
}

func TestClaudeCheckerNotStatusExits(t *testing.T) {
	dir := t.TempDir()
	fakeClaude := filepath.Join(dir, "claude")

	script := `#!/usr/bin/env sh
printf 'Claude Code v2.1.142\n'
printf 'Welcome back\n'
IFS= read -r command
# This is not /status, exit with failure
exit 3
`
	if err := os.WriteFile(fakeClaude, []byte(script), 0o755); err != nil {
		t.Fatalf("write fake claude: %v", err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	checker := NewClaudeChecker(fakeClaude)
	_, err := checker.Check(ctx)
	// This test expects the checker to NOT find expected usage output
	// so it should fail or timeout
	if err == nil {
		// The faker exited early so pty Read will return EOF
		// This is acceptable - we just want to verify non-/status input
		// causes the exit code 3 behavior
		t.Log("fake claude exited before /status as expected")
	}
}

func TestClaudeCheckerUsesEmptyCommandAsDefault(t *testing.T) {
	checker := NewClaudeChecker("")
	if checker.command != "claude" {
		t.Errorf("NewClaudeChecker(\"\") = %q; want %q", checker.command, "claude")
	}
}

func TestClaudeCheckerPreservesCommand(t *testing.T) {
	checker := NewClaudeChecker("/custom/path/claude")
	if checker.command != "/custom/path/claude" {
		t.Errorf("NewClaudeChecker(\"/custom/path/claude\") = %q; want %q", checker.command, "/custom/path/claude")
	}
}

func TestClaudeCheckerParsesCarriageReturnUsageScreen(t *testing.T) {
	dir := t.TempDir()
	fakeClaude := filepath.Join(dir, "claude")

	script := `#!/usr/bin/env sh
printf 'Claude Code v2.1.142\n'
printf 'Try /help\r'
sleep 0.1

IFS= read -r command
cmd="${command#?}"
if [ "$cmd" != "/status" ] && [ "$command" != "/status" ]; then
  printf 'unexpected command: %%s\n' "$command"
  exit 3
fi

# Print usage screen with carriage returns like real TUI
printf 'Current session:         [████████░░░░░░░░░░░░] 2%% used\r'
printf 'Resets 10am (Asia/Seoul)\r'
printf '\r'
printf 'Current week (all models): [███████████████░░░░░] 50%% used\r'
printf 'Resets May 16, 6pm (Asia/Seoul)\r'
printf '\r'

IFS= read -r exit_command
kill -P $$ 2>/dev/null || true
exit 0
`
	if err := os.WriteFile(fakeClaude, []byte(script), 0o755); err != nil {
		t.Fatalf("write fake claude: %v", err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	checker := NewClaudeChecker(fakeClaude)
	status, err := checker.Check(ctx)
	if err != nil {
		t.Fatalf("Check failed: %v", err)
	}
	if status.DailyLimit != "98%" {
		t.Fatalf("DailyLimit: got %q want %q", status.DailyLimit, "98%")
	}
	if status.DailyResetTime != "10am (Asia/Seoul)" {
		t.Fatalf("DailyResetTime: got %q want %q", status.DailyResetTime, "10am (Asia/Seoul)")
	}
	if status.WeeklyLimit != "50%" {
		t.Fatalf("WeeklyLimit: got %q want %q", status.WeeklyLimit, "50%")
	}
	if status.WeeklyResetTime != "May 16, 6pm (Asia/Seoul)" {
		t.Fatalf("WeeklyResetTime: got %q want %q", status.WeeklyResetTime, "May 16, 6pm (Asia/Seoul)")
	}
}
