"""
voice_output_design.py — MARK VII 音声出力モジュール（疑似実装）
Python 3.9 互換

Stage 1: macOS say コマンド（無料・即時・日本語OK）
Stage 2: ElevenLabs API（有料・リアル声・フォールバックあり）
Stage 3: 声クローン（Pro契約後・MARK VIII 以降）
"""

import subprocess
import sys
import json
import urllib.request
import urllib.error
from typing import Optional


# ============================================================
# Stage 1 — macOS say
# ============================================================

def speak(text: str, voice: str = "Kyoko", rate: int = 250) -> bool:
    """
    macOS say コマンドで非同期音声出力。
    他処理をブロックしない（Popen・wait しない）。

    Returns:
        True  … say プロセス起動成功
        False … エラー（say が存在しない等）
    """
    try:
        proc = subprocess.Popen(
            ["say", "-v", voice, "-r", str(rate), text],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )
        print(f"say起動 PID={proc.pid}")
        return True
    except FileNotFoundError:
        print("[ERROR] say コマンドが見つかりません（macOS 専用）", file=sys.stderr)
        return False
    except Exception as e:
        print(f"[ERROR] say 起動失敗: {e}", file=sys.stderr)
        return False


def speak_sync(text: str, voice: str = "Kyoko", rate: int = 250) -> bool:
    """
    同期版（読み上げ完了まで待つ）。
    ON/OFF 切替は呼び出し元で VOICE_ENABLED フラグを見る。
    """
    try:
        result = subprocess.run(
            ["say", "-v", voice, "-r", str(rate), text],
            capture_output=True,
        )
        return result.returncode == 0
    except FileNotFoundError:
        return False


def speak_to_file(text: str, path: str, voice: str = "Kyoko") -> bool:
    """
    音声を AIFF ファイルに保存（-o オプション）。
    """
    try:
        result = subprocess.run(
            ["say", "-v", voice, "-o", path, text],
            capture_output=True,
        )
        return result.returncode == 0
    except FileNotFoundError:
        return False


# ============================================================
# Stage 2 — ElevenLabs API（モック実装・実APIコールなし）
# ============================================================

ELEVENLABS_API_BASE = "https://api.elevenlabs.io/v1"

# 無料枠: ~10,000 字/月
# 日本語対応モデル: eleven_multilingual_v2
# Voice Cloning (IVC): Starter プラン以上 ($5/月)
# Professional Voice Clone (PVC): Pro プラン以上 ($22/月)
DEFAULT_MODEL = "eleven_multilingual_v2"


def speak_eleven(
    text: str,
    api_key: str,
    voice_id: str,
    model_id: str = DEFAULT_MODEL,
) -> Optional[bytes]:
    """
    ElevenLabs TTS API 呼び出し（モック）。
    実際の API コールは行わず、スキーマのみ示す。

    実装時は以下のエンドポイントを使用:
        POST /v1/text-to-speech/{voice_id}
        Headers: xi-api-key: <api_key>
        Body: { text, model_id, voice_settings: { stability, similarity_boost } }
        Response: audio/mpeg バイナリ

    Returns:
        bytes … 音声データ (MP3)
        None  … エラー
    """
    # --- モック: 実際は以下を urllib / requests で実装 ---
    print(f"[MOCK] ElevenLabs TTS: voice_id={voice_id}, chars={len(text)}")
    print(f"[MOCK] endpoint: POST {ELEVENLABS_API_BASE}/text-to-speech/{voice_id}")
    print(f"[MOCK] model: {model_id}")

    # 実装例（コメントアウト）:
    # url = f"{ELEVENLABS_API_BASE}/text-to-speech/{voice_id}"
    # payload = json.dumps({
    #     "text": text,
    #     "model_id": model_id,
    #     "voice_settings": {"stability": 0.5, "similarity_boost": 0.75},
    # }).encode()
    # req = urllib.request.Request(url, data=payload, method="POST")
    # req.add_header("xi-api-key", api_key)
    # req.add_header("Content-Type", "application/json")
    # with urllib.request.urlopen(req) as resp:
    #     return resp.read()

    return b"<mock_audio_bytes>"  # モック戻り値


# ============================================================
# Stage 2/3 — フォールバック統合
# ============================================================

# グローバル ON/OFF スイッチ（設定ファイルや env var から読む想定）
VOICE_ENABLED: bool = True
ELEVEN_API_KEY: str = ""   # 環境変数 ELEVEN_API_KEY から取得想定
ELEVEN_VOICE_ID: str = ""  # ユーザーのクローン voice_id


def speak_with_fallback(text: str, mode: str = "stage1") -> None:
    """
    音声出力のメインエントリポイント。

    mode:
        "stage1" — macOS say のみ（デフォルト）
        "stage2" — ElevenLabs → 失敗時 say にフォールバック
        "off"    — 無音（テキストのみ）

    使い方:
        speak_with_fallback("Jarvis の応答テキスト")
        speak_with_fallback("テキスト", mode="stage2")
    """
    if not VOICE_ENABLED or mode == "off":
        print(f"[VOICE OFF] {text[:40]}...")
        return

    if mode == "stage1":
        speak(text)
        return

    if mode == "stage2":
        if not ELEVEN_API_KEY or not ELEVEN_VOICE_ID:
            print("[STAGE2] API key 未設定 → say にフォールバック")
            speak(text)
            return

        audio_bytes = speak_eleven(text, ELEVEN_API_KEY, ELEVEN_VOICE_ID)
        if audio_bytes and audio_bytes != b"<mock_audio_bytes>":
            # 実装時: 音声バイトを afplay / pygame 等で再生
            print(f"[STAGE2] ElevenLabs 再生: {len(audio_bytes)} bytes")
        else:
            print("[STAGE2] ElevenLabs 失敗 → say にフォールバック")
            speak(text)
        return

    # 未知のモード
    print(f"[WARN] 不明なモード: {mode} → stage1 で続行")
    speak(text)


# ============================================================
# 設定サマリ（参照用）
# ============================================================

VOICE_CONFIG = {
    "stage1": {
        "engine": "macOS say",
        "voice": "Kyoko",  # ja_JP 女声（現環境で確認済）
        "rate": 250,
        "cost": "無料",
        "quality": "機械的",
        "latency": "即時（<100ms）",
        "available_voices_ja": ["Kyoko"],  # Otoya は macOS 15 で削除済
    },
    "stage2": {
        "engine": "ElevenLabs API",
        "model": "eleven_multilingual_v2",
        "free_tier": "10,000文字/月",
        "paid_plan": "Starter $5/月〜",
        "voice_cloning_plan": "Pro $22/月〜",
        "cost_per_1k_chars": "$0.30 (Starter)",
        "quality": "高品質・自然",
        "latency": "200〜500ms（API往復）",
    },
    "stage3": {
        "engine": "ElevenLabs Professional Voice Clone",
        "requirement": "Pro プラン + サンプル音声 30分以上",
        "timeline": "MARK VIII 以降",
    },
}


# ============================================================
# エントリポイント
# ============================================================

if __name__ == "__main__":
    print("=== MARK VII 音声出力モジュール 動作確認 ===\n")

    # Stage 1 動作確認
    print("[TEST] Stage 1: macOS say 非同期起動")
    ok = speak("テスト発話")
    print(f"       結果: {'成功' if ok else '失敗'}\n")

    # モック ElevenLabs
    print("[TEST] Stage 2: ElevenLabs モック")
    speak_eleven("テスト", api_key="mock_key", voice_id="mock_voice_id")
    print()

    # フォールバック確認
    print("[TEST] speak_with_fallback(stage1)")
    speak_with_fallback("Jarvis 音声テスト", mode="stage1")
    print()

    print("[TEST] speak_with_fallback(stage2, API key 未設定)")
    speak_with_fallback("フォールバックテスト", mode="stage2")
    print()

    # 設定サマリ
    print("[CONFIG] VOICE_CONFIG サマリ:")
    for stage, cfg in VOICE_CONFIG.items():
        print(f"  {stage}: {cfg.get('engine', '?')} / コスト: {cfg.get('cost', cfg.get('paid_plan', '?'))}")
