package cli_test

import (
	"fmt"
	"os"
	osexec "os/exec"
	"testing"
)

func TestRawTUIHelperProcess(t *testing.T) {
	switch {
	case os.Getenv("IOP_RAW_TUI_HELPER") == "1":
		runRawTUIHelper()
	case os.Getenv("IOP_CLAUDE_WARNING_HELPER") == "1":
		runClaudeWarningHelper()
	case os.Getenv("IOP_CLAUDE_TRUST_WARNING_HELPER") == "1":
		runClaudeTrustAndWarningHelper()
	case os.Getenv("IOP_CLAUDE_RATE_LIMIT_HELPER") == "1":
		runClaudeRateLimitHelper()
	case os.Getenv("IOP_CLAUDE_READY_REPLAY_HELPER") == "1":
		runClaudeReadyReplayHelper()
	default:
		return
	}
}

func setRawTerminal() {
	cmd := osexec.Command("stty", "raw", "-echo")
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		fmt.Fprintf(os.Stderr, "stty raw: %v\n", err)
		os.Exit(2)
	}
}

// emitStartupReady announces that the helper has finished configuring its raw
// terminal and is now reading input. Re-exec'd test binaries pay a variable
// cold-start cost, so the adapter's startup drain must observe this marker (and
// idle after it) before it writes the prompt. Otherwise the prompt would race
// terminal setup and be echoed or line-translated. The marker is consumed by
// the startup drain and never surfaces as a delta.
func emitStartupReady() {
	fmt.Fprint(os.Stdout, "iop-tui-ready\r\n")
	_ = os.Stdout.Sync()
}

func runRawTUIHelper() {
	setRawTerminal()
	emitStartupReady()
	readPromptAndReply()
}

func runClaudeWarningHelper() {
	setRawTerminal()
	fmt.Fprint(os.Stdout, "WARNING: Claude Code running in BypassPermissions mode\r\n2. Yes, I accept\r\n")
	_ = os.Stdout.Sync()

	want := []byte{'\x1b', '[', 'B', '\r'}
	seen := make([]byte, 0, len(want))
	buf := make([]byte, 1)
	for {
		n, err := os.Stdin.Read(buf)
		if n > 0 {
			seen = append(seen, buf[0])
			if len(seen) > len(want) {
				seen = seen[len(seen)-len(want):]
			}
			if string(seen) == string(want) {
				fmt.Fprint(os.Stdout, "ready\r\n")
				_ = os.Stdout.Sync()
				break
			}
		}
		if err != nil {
			os.Exit(0)
		}
	}

	readPromptAndReply()
}

func runClaudeTrustAndWarningHelper() {
	setRawTerminal()
	fmt.Fprint(os.Stdout, "Quick safety check: Is this a project you created or one you trust?\r\n❯ 1. Yes, I trust this folder\r\n2. No, exit\r\n")
	_ = os.Stdout.Sync()

	buf := make([]byte, 1)
	for {
		n, err := os.Stdin.Read(buf)
		if n > 0 && buf[0] == '\r' {
			break
		}
		if err != nil {
			os.Exit(0)
		}
	}

	fmt.Fprint(os.Stdout, "WARNING: Claude Code running in BypassPermissions mode\r\n2. Yes, I accept\r\n")
	_ = os.Stdout.Sync()

	want := []byte{'\x1b', '[', 'B', '\r'}
	seen := make([]byte, 0, len(want))
	for {
		n, err := os.Stdin.Read(buf)
		if n > 0 {
			seen = append(seen, buf[0])
			if len(seen) > len(want) {
				seen = seen[len(seen)-len(want):]
			}
			if string(seen) == string(want) {
				fmt.Fprint(os.Stdout, "ready\r\n")
				_ = os.Stdout.Sync()
				break
			}
		}
		if err != nil {
			os.Exit(0)
		}
	}

	readPromptAndReply()
}

func runClaudeRateLimitHelper() {
	setRawTerminal()
	emitStartupReady()
	for {
		buf := make([]byte, 1)
		n, err := os.Stdin.Read(buf)
		// Trigger on either terminator: in raw mode the adapter's terminator is
		// "\r", but if the prompt raced terminal setup the line discipline may have
		// translated it to "\n". Accepting both keeps session-limit detection
		// independent of the re-exec'd helper's cold-start timing.
		if n > 0 && (buf[0] == '\r' || buf[0] == '\n') {
			fmt.Fprint(os.Stdout, "\u25cf \u23bf  You've hit your session limit \u00b7 resets 12:50pm (Asia/Seoul)\r\n")
			fmt.Fprint(os.Stdout, "What do you want to do?\r\n❯ 1. Stop and wait for limit to reset\r\n2. Upgrade your plan\r\n")
			_ = os.Stdout.Sync()
		}
		if err != nil {
			os.Exit(0)
		}
	}
}

func runClaudeReadyReplayHelper() {
	setRawTerminal()
	emitStartupReady()

	// Handshake via blocking reads instead of a fixed sleep: swallow the first
	// prompt delivery to model a CLI that is not ready for input yet, then
	// advertise the ready screen so the adapter replays the prompt exactly once.
	_ = readRawLine() // first prompt, intentionally dropped before the ready screen

	fmt.Fprint(os.Stdout, "\r\n❯ ")
	_ = os.Stdout.Sync()

	prompt := readRawLine() // replayed prompt
	fmt.Fprintf(os.Stdout, "\r\n\u25cf replay:%s\r\n\u276f ", prompt)
	_ = os.Stdout.Sync()

	buf := make([]byte, 1)
	for {
		if _, err := os.Stdin.Read(buf); err != nil {
			os.Exit(0)
		}
	}
}

func readRawLine() string {
	var line []byte
	buf := make([]byte, 1)
	for {
		n, err := os.Stdin.Read(buf)
		if n > 0 {
			if buf[0] == '\r' || buf[0] == '\n' {
				return string(line)
			}
			line = append(line, buf[0])
		}
		if err != nil {
			os.Exit(0)
		}
	}
}

func readPromptAndReply() {
	var prompt []byte
	buf := make([]byte, 1)
	for {
		n, err := os.Stdin.Read(buf)
		if n > 0 {
			if buf[0] == '\r' {
				fmt.Fprintf(os.Stdout, "reply:%s\n", string(prompt))
				_ = os.Stdout.Sync()
				prompt = prompt[:0]
			} else {
				prompt = append(prompt, buf[0])
			}
		}
		if err != nil {
			os.Exit(0)
		}
	}
}
