#!/usr/bin/env python3
"""
G1 Voice Deploy — Record on workstation, deploy & play on G1 robot.
Also sets up mic on robot and manages remote recordings.

Usage (run from workstation):
  python3 g1_voice_deploy.py setup-mic          # Unmute & configure wireless mic on robot
  python3 g1_voice_deploy.py record --name hi    # Record from workstation mic, save locally
  python3 g1_voice_deploy.py record-robot --name arabic_greeting --seconds 5  # Record from robot mic
  python3 g1_voice_deploy.py play --name hi      # Deploy & play on G1 speaker
  python3 g1_voice_deploy.py play-robot --path "/home/unitree/SanadVoice/recorded voices/welcome_single.wav"
  python3 g1_voice_deploy.py tts --text "Hello"  # TTS on G1 speaker
  python3 g1_voice_deploy.py list                # List local recordings
  python3 g1_voice_deploy.py list-robot          # List recordings on robot
"""

import argparse
import os
import subprocess
import sys
import time
import wave
import numpy as np
from pathlib import Path

SCRIPT_DIR = Path(__file__).resolve().parent
DATA_DIR = SCRIPT_DIR / "DataG1"

ROBOT_IP = "192.168.123.164"
ROBOT_USER = "unitree"
ROBOT_PYTHON = "/home/unitree/miniconda3/envs/gemini/bin/python3"
ROBOT_VOICES_DIR = "/home/unitree/SanadVoice/recorded voices"
ROBOT_DDS_IFACE = "eth0"

SAMPLE_RATE = 16000
CHANNELS = 1
SAMPLE_WIDTH = 2
CHUNK_SIZE = 4096

SSH_OPTS = ["-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=5"]


def ssh_cmd(cmd: str, capture=True):
    """Run a command on the robot via SSH."""
    target = f"{ROBOT_USER}@{ROBOT_IP}"
    result = subprocess.run(
        ["ssh"] + SSH_OPTS + [target, cmd],
        capture_output=capture, text=True,
    )
    if capture:
        return result.returncode, result.stdout.strip(), result.stderr.strip()
    return result.returncode, "", ""


def scp_to_robot(local_path: str, remote_path: str):
    """Copy a file to the robot."""
    target = f"{ROBOT_USER}@{ROBOT_IP}"
    ret = subprocess.run(
        ["scp"] + SSH_OPTS + [local_path, f"{target}:{remote_path}"],
        capture_output=True, text=True,
    )
    return ret.returncode == 0


def scp_from_robot(remote_path: str, local_path: str):
    """Copy a file from the robot."""
    target = f"{ROBOT_USER}@{ROBOT_IP}"
    ret = subprocess.run(
        ["scp"] + SSH_OPTS + [f"{target}:{remote_path}", local_path],
        capture_output=True, text=True,
    )
    return ret.returncode == 0


# ─── SETUP MIC ──────────────────────────────────────────────

def cmd_setup_mic(args):
    """Unmute and configure the wireless mic on the robot."""
    print("Setting up wireless mic on G1...\n")

    # List sources
    code, out, _ = ssh_cmd("pactl list sources short")
    print("PulseAudio sources:")
    print(out)
    print()

    # Find wireless mic source
    mic_source = None
    for line in out.split("\n"):
        if "Wireless" in line or "Hollyland" in line:
            mic_source = line.split("\t")[1] if "\t" in line else None
            mic_index = line.split("\t")[0] if "\t" in line else None
            break

    if not mic_source:
        print("Wireless mic not found. Is it plugged in?")
        return

    print(f"Found mic: {mic_source} (index {mic_index})")

    # Set as default, unmute, set volume
    ssh_cmd(f"pactl set-default-source {mic_source}")
    print("  Set as default source.")

    ssh_cmd(f"pactl set-source-mute {mic_index} 0")
    print("  Unmuted.")

    ssh_cmd(f"pactl set-source-volume {mic_index} 100%")
    print("  Volume set to 100%.")

    # Check mute status
    code, out, _ = ssh_cmd(f'pactl list sources | grep -A 3 "Wireless" | grep Mute')
    print(f"  Status: {out.strip()}")

    # Verify recording
    print("\n  Verifying mic (2 second test)...")
    ssh_cmd("timeout 2 parec -d 3 --format=s16le --rate=16000 --channels=1 --raw > /tmp/_mic_verify.pcm")
    code, out, _ = ssh_cmd(
        f'{ROBOT_PYTHON} -c "'
        'import numpy as np;'
        'a = np.fromfile(\'/tmp/_mic_verify.pcm\', dtype=np.int16);'
        'print(f\'samples={len(a)} std={a.std():.0f}\')'
        '"'
    )
    print(f"  Result: {out}")
    if "std=0" in out:
        print("  WARNING: Mic is silent! Check transmitter is powered on.")
    else:
        print("  Mic is working!")


# ─── RECORD ON WORKSTATION ───────────────────────────────────

def cmd_record(args):
    """Record from workstation microphone."""
    import pyaudio

    DATA_DIR.mkdir(parents=True, exist_ok=True)
    out_path = DATA_DIR / f"{args.name}.wav"

    pa = pyaudio.PyAudio()

    # Find best input device
    device_index = args.device
    if device_index is None:
        for i in range(pa.get_device_count()):
            info = pa.get_device_info_by_index(i)
            if info["maxInputChannels"] > 0:
                name = info["name"]
                if name in ("default", "pipewire") or "pulse" in name.lower():
                    device_index = i
                    break
        if device_index is None:
            for i in range(pa.get_device_count()):
                if pa.get_device_info_by_index(i)["maxInputChannels"] > 0:
                    device_index = i
                    break

    # Find supported rate
    info = pa.get_device_info_by_index(device_index)
    rec_rate = int(info["defaultSampleRate"])
    print(f"Using device [{device_index}] {info['name']} @ {rec_rate}Hz")
    print(f"Recording {args.seconds}s... speak now!\n")

    stream = pa.open(
        format=pyaudio.paInt16, channels=1, rate=rec_rate,
        input=True, input_device_index=device_index, frames_per_buffer=CHUNK_SIZE,
    )

    frames = []
    total = int(rec_rate / CHUNK_SIZE * args.seconds)
    start = time.time()
    for i in range(total):
        frames.append(stream.read(CHUNK_SIZE, exception_on_overflow=False))
        elapsed = time.time() - start
        pct = min(elapsed / args.seconds, 1.0)
        bar = "█" * int(30 * pct) + "░" * (30 - int(30 * pct))
        print(f"\r  [{bar}] {elapsed:.1f}s / {args.seconds:.1f}s", end="", flush=True)

    stream.stop_stream()
    stream.close()
    pa.terminate()
    print()

    # Resample to 16kHz
    audio = np.frombuffer(b"".join(frames), dtype=np.int16)
    if rec_rate != SAMPLE_RATE:
        tl = int(len(audio) * SAMPLE_RATE / rec_rate)
        audio = np.interp(
            np.linspace(0, len(audio), tl, endpoint=False),
            np.arange(len(audio)), audio.astype(np.float64),
        ).astype(np.int16)

    wf = wave.open(str(out_path), "wb")
    wf.setnchannels(1)
    wf.setsampwidth(2)
    wf.setframerate(SAMPLE_RATE)
    wf.writeframes(audio.tobytes())
    wf.close()

    print(f"Saved: {out_path} ({len(audio)/SAMPLE_RATE:.1f}s, {out_path.stat().st_size/1024:.1f}KB)")


# ─── RECORD ON ROBOT ─────────────────────────────────────────

def cmd_record_robot(args):
    """Record from the wireless mic on the robot."""
    DATA_DIR.mkdir(parents=True, exist_ok=True)
    seconds = args.seconds
    remote_wav = f"/tmp/_rec_{args.name}.wav"
    local_path = DATA_DIR / f"{args.name}.wav"

    record_code = (
        f"import time, subprocess, wave, numpy as np;"
        f"print('Recording {seconds}s... speak now!');"
        f"proc = subprocess.Popen(['parec','-d','3','--format=s16le','--rate=16000','--channels=1','--raw'], stdout=subprocess.PIPE);"
        f"time.sleep({seconds});"
        f"proc.terminate();"
        f"raw = proc.stdout.read();"
        f"audio = np.frombuffer(raw, dtype=np.int16);"
        f"print(f'Recorded {{len(audio)}} samples, std={{audio.std():.0f}}');"
        f"wf = wave.open('{remote_wav}', 'wb');"
        f"wf.setnchannels(1); wf.setsampwidth(2); wf.setframerate(16000);"
        f"wf.writeframes(audio.tobytes()); wf.close();"
        f"print('Saved on robot')"
    )

    print(f"Recording {seconds}s from robot wireless mic...")
    code, out, err = ssh_cmd(f'{ROBOT_PYTHON} -c "{record_code}"')
    print(f"  {out}")
    if err:
        print(f"  stderr: {err}")

    # Download to workstation
    print(f"Downloading to {local_path}...")
    if scp_from_robot(remote_wav, str(local_path)):
        print(f"  Saved: {local_path}")
        ssh_cmd(f"rm -f {remote_wav}")
    else:
        print("  Download failed!")

    # Also save on robot in recorded voices dir
    remote_final = f"{ROBOT_VOICES_DIR}/{args.name}.wav"
    ssh_cmd(f'cp "{remote_wav}" "{remote_final}" 2>/dev/null')


# ─── PLAY ON G1 ──────────────────────────────────────────────

def cmd_play(args):
    """Deploy a local WAV file to G1 and play on built-in speaker."""
    wav_path = DATA_DIR / f"{args.name}.wav"
    if not wav_path.exists():
        print(f"Not found: {wav_path}")
        cmd_list(args)
        return

    with wave.open(str(wav_path), "rb") as wf:
        dur = wf.getnframes() / wf.getframerate()
    print(f"Loaded: {wav_path.name} ({dur:.1f}s)")

    # SCP to robot
    remote_wav = f"/tmp/_play_{args.name}.wav"
    print(f"Copying to robot...")
    if not scp_to_robot(str(wav_path), remote_wav):
        print("  SCP failed!")
        return
    print("  Copied.")

    # Play on robot
    _play_remote_wav(remote_wav, dur)

    # Cleanup
    ssh_cmd(f"rm -f {remote_wav}")


def cmd_play_robot(args):
    """Play a WAV file that already exists on the robot."""
    # Get duration
    code, out, _ = ssh_cmd(
        f'{ROBOT_PYTHON} -c "import wave; '
        f"wf = wave.open('{args.path}', 'rb'); "
        f"print(wf.getnframes() / wf.getframerate()); wf.close()"
        '"'
    )
    try:
        dur = float(out)
    except ValueError:
        print(f"Cannot read: {args.path}")
        print(f"  {out}")
        return

    print(f"Playing: {args.path} ({dur:.1f}s)")
    _play_remote_wav(args.path, dur)


def _play_remote_wav(remote_wav: str, duration: float):
    """Play a WAV file on the G1 speaker (file must already be on robot)."""
    play_code = (
        "import time, wave, json, numpy as np;"
        "from unitree_sdk2py.core.channel import ChannelFactoryInitialize;"
        "from unitree_sdk2py.g1.audio.g1_audio_client import AudioClient;"
        "from unitree_sdk2py.g1.audio.g1_audio_api import *;"
        f"ChannelFactoryInitialize(0, '{ROBOT_DDS_IFACE}');"
        "c = AudioClient(); c.SetTimeout(10.0); c.Init(); c.SetVolume(100);"
        "c._Call(ROBOT_API_ID_AUDIO_STOP_PLAY, json.dumps({'app_name':'p'}));"
        "time.sleep(0.3);"
        f"wf = wave.open('{remote_wav}', 'rb');"
        "rate = wf.getframerate(); nch = wf.getnchannels();"
        "audio = np.frombuffer(wf.readframes(wf.getnframes()), dtype=np.int16); wf.close();"
        "audio = audio.reshape(-1, 2).mean(axis=1).astype(np.int16) if nch == 2 else audio;"
        "tl = int(len(audio) * 16000 / rate) if rate != 16000 else len(audio);"
        "audio = np.interp(np.linspace(0, len(audio), tl, endpoint=False), np.arange(len(audio)), audio.astype(np.float64)).astype(np.int16) if rate != 16000 else audio;"
        "pcm = audio.tobytes();"
        "sid = f's_{int(time.time()*1000)}';"
        "param = json.dumps({'app_name':'p','stream_id':sid,'sample_rate':16000,'channels':1,'bits_per_sample':16});"
        "c._CallRequestWithParamAndBin(ROBOT_API_ID_AUDIO_START_PLAY, param, list(pcm));"
        f"time.sleep({duration + 1});"
        "c._Call(ROBOT_API_ID_AUDIO_STOP_PLAY, json.dumps({'app_name':'p'}));"
        "print('played')"
    )

    print(f"Playing on G1... ({duration:.1f}s)")
    proc = subprocess.Popen(
        ["ssh"] + SSH_OPTS + [f"{ROBOT_USER}@{ROBOT_IP}",
         f'{ROBOT_PYTHON} -c "{play_code}"'],
        stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
    )

    start = time.time()
    while proc.poll() is None:
        elapsed = time.time() - start
        pct = min(elapsed / duration, 1.0)
        bar = "█" * int(30 * pct) + "░" * (30 - int(30 * pct))
        print(f"\r  [{bar}] {min(elapsed, duration):.1f}s / {duration:.1f}s", end="", flush=True)
        time.sleep(0.2)

    out = proc.stdout.read().decode().strip()
    if "played" in out:
        print(f"\n  Done.")
    else:
        print(f"\n  Robot output: {out}")


# ─── TTS ──────────────────────────────────────────────────────

def cmd_tts(args):
    """Play text-to-speech on G1 speaker (English/Chinese only)."""
    tts_code = (
        "import json;"
        "from unitree_sdk2py.core.channel import ChannelFactoryInitialize;"
        "from unitree_sdk2py.g1.audio.g1_audio_client import AudioClient;"
        f"ChannelFactoryInitialize(0, '{ROBOT_DDS_IFACE}');"
        "c = AudioClient(); c.SetTimeout(10.0); c.Init();"
        f"c.SetVolume({args.volume});"
        f"c.TtsMaker('{args.text}', {args.speaker_id});"
        "print('ok')"
    )
    print(f"TTS: \"{args.text}\" (speaker_id={args.speaker_id}, vol={args.volume})")
    code, out, _ = ssh_cmd(f'{ROBOT_PYTHON} -c "{tts_code}"')
    if "ok" in out:
        print("  Sent.")
    else:
        print(f"  Result: {out}")


# ─── LIST ─────────────────────────────────────────────────────

def cmd_list(args):
    """List local recordings."""
    DATA_DIR.mkdir(parents=True, exist_ok=True)
    wavs = sorted(DATA_DIR.glob("*.wav"))
    if not wavs:
        print(f"No recordings in {DATA_DIR}")
        return
    print(f"\n{'#':<4} {'Name':<35} {'Duration':>8} {'Size':>10}")
    print("-" * 60)
    for i, p in enumerate(wavs):
        try:
            with wave.open(str(p), "rb") as wf:
                dur = wf.getnframes() / wf.getframerate()
            size = p.stat().st_size / 1024
            print(f"{i:<4} {p.stem:<35} {dur:>6.1f}s {size:>8.1f}KB")
        except Exception:
            print(f"{i:<4} {p.stem:<35} {'ERROR':>8}")
    print()


def cmd_list_robot(args):
    """List recordings on the robot."""
    code, out, _ = ssh_cmd(
        f'{ROBOT_PYTHON} -c "'
        "import wave, os; from pathlib import Path;"
        f"d = Path('{ROBOT_VOICES_DIR}');"
        "wavs = sorted(d.glob('*.wav'));"
        "print(f'{{len(wavs)}} files in {d}');"
        "for p in wavs:"
        "    try:"
        "        wf = wave.open(str(p),'rb'); dur = wf.getnframes()/wf.getframerate(); wf.close();"
        "        print(f'{p.stem:<45} {dur:>6.1f}s')"
        "    except: print(f'{p.stem:<45} ERROR')"
        '"'
    )
    print(out)


# ─── MAIN ─────────────────────────────────────────────────────

def main():
    parser = argparse.ArgumentParser(
        description="G1 Voice Deploy — Record, deploy & play audio on G1 robot",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  %(prog)s setup-mic
  %(prog)s record --name greeting --seconds 5
  %(prog)s record-robot --name arabic_hi --seconds 5
  %(prog)s play --name greeting
  %(prog)s play-robot --path "/home/unitree/SanadVoice/recorded voices/welcome_single.wav"
  %(prog)s tts --text "Hello, welcome!"
  %(prog)s list
  %(prog)s list-robot
        """,
    )

    sub = parser.add_subparsers(dest="command")

    # setup-mic
    sub.add_parser("setup-mic", help="Unmute & configure wireless mic on robot")

    # record
    p = sub.add_parser("record", help="Record from workstation mic")
    p.add_argument("--name", "-n", required=True, help="Recording name")
    p.add_argument("--seconds", "-s", type=float, default=5.0)
    p.add_argument("--device", "-d", type=int, default=None)

    # record-robot
    p = sub.add_parser("record-robot", help="Record from robot wireless mic")
    p.add_argument("--name", "-n", required=True, help="Recording name")
    p.add_argument("--seconds", "-s", type=float, default=5.0)

    # play
    p = sub.add_parser("play", help="Deploy & play local WAV on G1 speaker")
    p.add_argument("--name", "-n", required=True, help="Recording name")

    # play-robot
    p = sub.add_parser("play-robot", help="Play a WAV file already on robot")
    p.add_argument("--path", "-p", required=True, help="Full path on robot")

    # tts
    p = sub.add_parser("tts", help="Text-to-speech on G1 (English/Chinese)")
    p.add_argument("--text", "-t", required=True, help="Text to speak")
    p.add_argument("--speaker-id", type=int, default=0, help="Voice ID (0,1,2)")
    p.add_argument("--volume", "-v", type=int, default=100)

    # list
    sub.add_parser("list", help="List local recordings")
    sub.add_parser("list-robot", help="List recordings on robot")

    args = parser.parse_args()

    commands = {
        "setup-mic": cmd_setup_mic,
        "record": cmd_record,
        "record-robot": cmd_record_robot,
        "play": cmd_play,
        "play-robot": cmd_play_robot,
        "tts": cmd_tts,
        "list": cmd_list,
        "list-robot": cmd_list_robot,
    }

    if args.command in commands:
        commands[args.command](args)
    else:
        parser.print_help()


if __name__ == "__main__":
    main()
