#!/usr/bin/env python3
"""
Remote Claude Terminal — client for the SFTP-based Claude Code remote driver.

Runs on any PC with Python 3 + sshpass.  Sends prompts to claude_buffer_in and
shows responses from claude_buffer_out via the SFTP server.

Usage:
    python3 remote_claude_terminal.py
    python3 remote_claude_terminal.py --password Temp123$
    python3 remote_claude_terminal.py --host myserver.com --port 221 --user me
"""

import subprocess
import sys
import time
import hashlib
import os
import argparse
import tempfile
import getpass
import shutil

# ── defaults ──────────────────────────────────────────────────────────────────
HOST       = "testing.tigerroad.net"
PORT       = "221"
USER       = "alupeta"
REMOTE_DIR = "/html/remote_driver"
POLL_S     = 4      # seconds between polls while waiting for response
TIMEOUT_S  = 600    # max seconds to wait for a response (10 min)


def parse_args():
    p = argparse.ArgumentParser(description=__doc__,
                                formatter_class=argparse.RawDescriptionHelpFormatter)
    p.add_argument("--host",     default=HOST)
    p.add_argument("--port",     default=PORT)
    p.add_argument("--user",     default=USER)
    p.add_argument("--password", default=None, help="SFTP password (prompted if omitted)")
    p.add_argument("--remote-dir", default=REMOTE_DIR, dest="remote_dir")
    p.add_argument("--poll",     default=POLL_S,    type=float, dest="poll",
                   help=f"Poll interval in seconds (default {POLL_S})")
    p.add_argument("--timeout",  default=TIMEOUT_S, type=int,
                   help=f"Max wait for response in seconds (default {TIMEOUT_S})")
    return p.parse_args()


# ── SFTP helpers ──────────────────────────────────────────────────────────────

def _sftp(args, password, cmds: str) -> bool:
    """Run one or more sftp commands by piping them via stdin (sshpass-compatible)."""
    result = subprocess.run(
        [
            "sshpass", "-p", password, "sftp",
            "-o", "StrictHostKeyChecking=no",
            "-o", "PreferredAuthentications=password",
            "-P", args.port,
            f"{args.user}@{args.host}",
        ],
        input=cmds.encode(),
        capture_output=True,
        timeout=30,
    )
    return result.returncode == 0


def sftp_get(args, password, remote, local) -> bool:
    return _sftp(args, password, f"get {remote} {local}\n")


def sftp_put(args, password, local, remote) -> bool:
    return _sftp(args, password, f"put {local} {remote}\n")


def file_hash(path: str) -> str:
    try:
        with open(path, "rb") as f:
            return hashlib.md5(f.read()).hexdigest()
    except Exception:
        return ""


def read_file(path: str) -> str:
    try:
        with open(path, encoding="utf-8", errors="replace") as f:
            return f.read()
    except Exception:
        return ""


# ── main loop ─────────────────────────────────────────────────────────────────

def main():
    args = parse_args()
    if args.password is None:
        args.password = getpass.getpass(f"SFTP password for {args.user}@{args.host}: ")

    tmpdir = tempfile.mkdtemp(prefix="rct_")
    local_in  = os.path.join(tmpdir, "in.txt")
    local_out = os.path.join(tmpdir, "out.txt")

    print(f"\n{'='*56}")
    print(f"  Remote Claude Terminal")
    print(f"  {args.user}@{args.host}:{args.port}  dir={args.remote_dir}")
    print(f"{'='*56}")
    print("  Type any prompt and press Enter.")
    print("  'exit' / Ctrl-C to quit.\n")

    # Fetch the current output so we know where to detect NEW responses from
    sftp_get(args, args.password, f"{args.remote_dir}/claude_buffer_out", local_out)
    baseline_out = file_hash(local_out)
    first_out = read_file(local_out).strip()
    if first_out:
        print(f"[Last server response]\n{first_out}\n{'─'*56}")

    try:
        while True:
            # ── get user prompt ───────────────────────────────────────────────
            try:
                raw = input("\n> ").strip()
            except (EOFError, KeyboardInterrupt):
                print("\nExiting.")
                break

            if not raw:
                continue
            if raw.lower() in ("exit", "quit", "q", "\\q"):
                print("Exiting.")
                break

            # ── snapshot current output hash BEFORE uploading ─────────────────
            sftp_get(args, args.password, f"{args.remote_dir}/claude_buffer_out", local_out)
            pre_hash = file_hash(local_out)

            # ── upload prompt to claude_buffer_in ────────────────────────────
            with open(local_in, "w") as f:
                f.write(raw + "\n")

            if not sftp_put(args, args.password, local_in, f"{args.remote_dir}/claude_buffer_in"):
                print("[ERROR] upload failed — check connection")
                continue

            print(f"[Sent — waiting for response (timeout {args.timeout}s)]")

            # ── poll claude_buffer_out for the response ──────────────────────
            deadline   = time.time() + args.timeout
            spin_chars = "|/-\\"
            spin_i     = 0
            last_hash  = pre_hash
            last_shown = ""

            while time.time() < deadline:
                time.sleep(args.poll)

                if not sftp_get(args, args.password,
                                f"{args.remote_dir}/claude_buffer_out", local_out):
                    print("\r[poll failed — retrying]    ", end="", flush=True)
                    continue

                cur_hash = file_hash(local_out)
                if cur_hash == last_hash:
                    # No change — show spinner
                    print(f"\r  {spin_chars[spin_i % len(spin_chars)]}  waiting...", end="",
                          flush=True)
                    spin_i += 1
                    continue

                last_hash = cur_hash
                content   = read_file(local_out)

                if "[Processing" in content and "[Done" not in content:
                    # Daemon acknowledged but still working
                    print(f"\r  {spin_chars[spin_i % len(spin_chars)]}  processing...",
                          end="", flush=True)
                    spin_i += 1
                    continue

                # Full response (or changed content without explicit [Done])
                if content != last_shown:
                    last_shown = content
                    print(f"\r{' '*40}\r")  # clear spinner line
                    print(f"{'─'*56}")
                    print(content.strip())
                    print(f"{'─'*56}")

                if "[Done" in content:
                    break
                # If no [Done] marker but content is stable, keep polling briefly
            else:
                print(f"\n[Timeout after {args.timeout}s — last output shown above]")

    except KeyboardInterrupt:
        print("\nInterrupted.")
    finally:
        shutil.rmtree(tmpdir, ignore_errors=True)


if __name__ == "__main__":
    main()
