"""
voice_output.py — MARK VII 音声出力モジュール
Python 3.9 互換

Stage 1: macOS say コマンド（無料・即時・日本語OK）
Stage 2: ElevenLabs API（モック実装・将来拡張フック）
Stage 3: 声クローン（MARK VIII 以降）

並行出力・部分失敗耐性・ログ・キャッシュ刷新
"""

import os
import subprocess
import sys
from typing import Dict, Optional

# ============================================================
# 定数
# ============================================================

DEFAULT_VOICE = "Kyoko"
DEFAULT_RATE = 250
ELEVENLABS_API_BASE = "https://api.elevenlabs.io/v1"
DEFAULT_ELEVEN_MODEL = "eleven_multilingual_v2"


# ============================================================
# 環境変数チェック
# ============================================================

def is_voice_enabled() -> bool:
    """
    環境変数 VOICE_ENABLED と OS確認。
    VOICE_ENABLED=0 で完全 OFF。
    macOS 以外（say コマンドなし）でも False を返す。
    """
    # 環境変数で明示 OFF
    if os.environ.get("VOICE_ENABLED", "1") == "0":
        return False

    # macOS か確認（darwin のみ say が存在）
    if sys.platform != "darwin":
        return False

    # say コマンド存在確認
    result = subprocess.run(
        ["which", "say"],
        capture_output=True,
        text=True,
    )
    return result.returncode == 0


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

def speak(
    text: str,
    voice: Optional[str] = None,
    rate: int = DEFAULT_RATE,
    blocking: bool = False,
) -> Optional[int]:
    """
    macOS say で text を発話。Popen 非同期。
    blocking=True なら subprocess.run で完了待機。
    voice_enabled=False なら何もしない。

    戻り値:
        PID (非同期時) / returncode (blocking時) / None (disabled時)

    環境変数:
        VOICE_ENABLED=0  → 完全 OFF（say も叩かない）
        VOICE_VOICE      → 音声切替（デフォルト Kyoko）
    """
    if not is_voice_enabled():
        return None

    # 環境変数 VOICE_VOICE が設定されていれば優先
    resolved_voice = voice or os.environ.get("VOICE_VOICE", DEFAULT_VOICE)
    cmd = ["say", "-v", resolved_voice, "-r", str(rate), text]

    try:
        if blocking:
            result = subprocess.run(
                cmd,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
            )
            return result.returncode
        else:
            proc = subprocess.Popen(
                cmd,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
            )
            return proc.pid

    except FileNotFoundError:
        print("[voice_output] ERROR: say コマンドが見つかりません", file=sys.stderr)
        return None
    except Exception as e:
        print(f"[voice_output] ERROR: say 起動失敗: {e}", file=sys.stderr)
        return None


# ============================================================
# Stage 2 — ElevenLabs API（モック実装）
# ============================================================

def speak_eleven_mock(
    text: str,
    api_key: str,
    voice_id: str,
    model_id: str = DEFAULT_ELEVEN_MODEL,
) -> Dict:
    """
    ElevenLabs API モック・将来実装フック。
    現在は実APIコールしない。呼ぶと NotImplementedError 風 dict を返す。

    将来実装時のエンドポイント:
        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 バイナリ

    戻り値:
        {
            "status": "not_implemented",
            "reason": str,
            "endpoint": str,
            "model": str,
            "chars": int,
        }
    """
    endpoint = f"{ELEVENLABS_API_BASE}/text-to-speech/{voice_id}"
    return {
        "status": "not_implemented",
        "reason": "ElevenLabs Stage 2 は MARK VIII 以降で実装予定。現在はモック。",
        "endpoint": endpoint,
        "model": model_id,
        "chars": len(text),
    }


# ============================================================
# フォールバック統合エントリ（オプション）
# ============================================================

def speak_with_fallback(
    text: str,
    voice: Optional[str] = None,
    rate: int = DEFAULT_RATE,
) -> Optional[int]:
    """
    Stage 1（macOS say）優先。ElevenLabs は未実装のためフォールバックなし。
    voice_enabled=False なら None を返す。
    """
    return speak(text, voice=voice, rate=rate, blocking=False)


# ============================================================
# self-test
# ============================================================

if __name__ == "__main__":
    import time
    import signal

    print("=== voice_output.py self-test ===\n")
    all_pass = True

    # [1] is_voice_enabled
    enabled = is_voice_enabled()
    print(f"[1] is_voice_enabled() = {enabled}")
    if sys.platform == "darwin" and os.environ.get("VOICE_ENABLED", "1") != "0":
        if not enabled:
            print("    WARN: macOS だが say が見つからない（インストール確認要）")
            # テスト続行（is_voice_enabled=False でも他テスト可）
    print()

    # [2] speak() 非同期 PID 取得
    print("[2] speak('テスト発話', blocking=False)")
    if enabled:
        pid = speak("テスト発話", blocking=False)
        print(f"    PID = {pid}")
        if pid is not None:
            # kill -0 で生存確認（即終了している可能性も正常）
            time.sleep(0.05)
            try:
                os.kill(pid, 0)
                print(f"    プロセス PID={pid} 生存中")
            except ProcessLookupError:
                print(f"    プロセス PID={pid} は既に終了（正常：短文は即完了）")
            except PermissionError:
                print(f"    プロセス PID={pid} 存在（権限なしで確認）")
        else:
            print("    WARN: PID 取得失敗")
            all_pass = False
    else:
        print("    SKIP: voice disabled (VOICE_ENABLED=0 or non-macOS)")
    print()

    # [3] VOICE_ENABLED=0 で None が返る
    print("[3] VOICE_ENABLED=0 のとき speak() = None")
    original = os.environ.get("VOICE_ENABLED")
    os.environ["VOICE_ENABLED"] = "0"
    result_disabled = speak("無効テスト")
    if original is None:
        del os.environ["VOICE_ENABLED"]
    else:
        os.environ["VOICE_ENABLED"] = original
    if result_disabled is None:
        print("    OK: None が返った")
    else:
        print(f"    FAIL: {result_disabled} が返った（None 期待）")
        all_pass = False
    print()

    # [4] speak_eleven_mock
    print("[4] speak_eleven_mock テスト")
    mock_result = speak_eleven_mock(
        text="テスト",
        api_key="mock_key",
        voice_id="mock_voice_id",
    )
    assert mock_result["status"] == "not_implemented", f"FAIL: {mock_result}"
    assert mock_result["chars"] == 3, f"FAIL chars: {mock_result}"
    print(f"    status = {mock_result['status']}")
    print(f"    chars  = {mock_result['chars']}")
    print(f"    OK: not_implemented dict が返った")
    print()

    # 結果
    if all_pass:
        print("=== ALL PASS ===")
        sys.exit(0)
    else:
        print("=== SOME TESTS FAILED ===")
        sys.exit(1)
