import os
import sys
import re

missing = []
for lib in ["elevenlabs"]:
    try:
        __import__(lib)
    except ImportError:
        missing.append(lib)

if missing:
    print(f"Error: Missing libraries:\n{', '.join(missing)}")
    print("  install: pip install elevenlabs")
    input("\nPress ENTER to terminate>>\n")
    sys.exit(1)

from elevenlabs import VoiceSettings
from elevenlabs.client import ElevenLabs

from tktts_base import apply_replacements, normalize_speaker, split_dialogue


TTS_ENGINE_NAME = "elevenlabs"

DEFAULT_MODEL_ID = "eleven_multilingual_v2"
DEFAULT_OUTPUT_FORMAT = "mp3_44100_128"
DEFAULT_ELEVENLABS_VOICE = "Rachel"

DEFAULT_VOICE_SETTINGS = {
    "stability": 0.60,
    "similarity_boost": 0.75,
    "style": 0.0,
    "use_speaker_boost": True,
}


DEFAULT_API_KEY = os.getenv("ELEVENLABS_API_KEY")
if DEFAULT_API_KEY is None:
    print("❌ ELEVENLABS_API_KEY が設定されていません")
else:
    print("API key loaded:", DEFAULT_API_KEY[:5] + "...")

def _get_client(api_key=None):
    if api_key:
        return ElevenLabs(api_key=api_key)
    return ElevenLabs(api_key=os.getenv("ELEVENLABS_API_KEY"))


def _make_voice_settings(voice_settings=None):
    vs = dict(DEFAULT_VOICE_SETTINGS)
    if voice_settings:
        vs.update(voice_settings)

    allowed = {
        "stability",
        "similarity_boost",
        "style",
        "use_speaker_boost",
        "speed",
    }
    vs = {k: v for k, v in vs.items() if k in allowed and v is not None}
    return VoiceSettings(**vs)


def _write_audio_to_file(audio, outfile):
    total = 0
    with open(outfile, "wb") as f:
        if isinstance(audio, (bytes, bytearray)):
            f.write(audio)
            return len(audio)

        for chunk in audio:
            if chunk:
                f.write(chunk)
                total += len(chunk)
    return total


def get_available_voices_info(api_key=None, page_size=100):
    client = _get_client(api_key)
    voices = []
    next_page_token = None

    while True:
        res = client.voices.search(
            page_size=page_size,
            next_page_token=next_page_token,
        )

        for v in getattr(res, "voices", []) or []:
            voices.append({
                "name": getattr(v, "name", ""),
                "id": getattr(v, "voice_id", None) or getattr(v, "id", None),
                "category": getattr(v, "category", None),
                "labels": getattr(v, "labels", None),
                "description": getattr(v, "description", None),
            })

        if not getattr(res, "has_more", False):
            break

        next_page_token = getattr(res, "next_page_token", None)
        if not next_page_token:
            break

    return voices


def get_available_voices(api_key=None):
    voices = get_available_voices_info(api_key)
    if not voices:
        return False
    return [v["name"] for v in voices]


def list_available_voices(api_key=None):
    print(f"=== 利用可能な {TTS_ENGINE_NAME} voices ===")
    voices = get_available_voices_info(api_key)

    if not voices:
        print("  (no voices found)")
        return False

    for v in voices:
        print(
            f"  Name: {v['name']}, ID: {v['id']}, "
            f"Category: {v.get('category')}, Labels: {v.get('labels')}"
        )

    return True


def resolve_speaker_id(speaker_name, api_key=None, voices_dict=None):
    if voices_dict is None:
        voices_dict = get_available_voices_info(api_key)

    speaker_name_norm = normalize_speaker(str(speaker_name)).lower()

    for v in voices_dict:
        if speaker_name_norm == (v["name"] or "").lower():
            return voices_dict, v["id"]

    for v in voices_dict:
        if speaker_name_norm in (v["name"] or "").lower():
            return voices_dict, v["id"]

    raise ValueError(
        f"❌ Error in tktts_elevenlabs.resolve_speaker_id(): "
        f"話者 [{speaker_name}] が見つかりませんでした"
    )


def _pick_default_voice_id(voices_dict):
    if not voices_dict:
        return None

    want = DEFAULT_ELEVENLABS_VOICE.lower()
    for v in voices_dict:
        if (v["name"] or "").lower() == want:
            return v["id"]

    return voices_dict[0]["id"]


def _is_voice_id(s):
    return isinstance(s, str) and re.fullmatch(r"[A-Za-z0-9_-]{10,}", s)


def speak(
    outfile,
    text,
    voice=None,
    speak_rate=None,
    speak_pitch=None,
    api_key=None,
    model_id=DEFAULT_MODEL_ID,
    voice_settings=None,
    output_format=DEFAULT_OUTPUT_FORMAT,
    optimize_streaming_latency=None,
    language_code=None,
):
    client = _get_client(api_key)

    if speak_pitch is not None:
        print("  [warn] ElevenLabs では speak_pitch の直接指定は基本未対応のため無視します。")

    if speak_rate is not None:
        print("  [warn] speak_rate は ElevenLabs の speed に変換して試します。")
        if voice_settings is None:
            voice_settings = {}
        voice_settings["speed"] = float(speak_rate)

    voices_dict = get_available_voices_info(api_key)
    if not voices_dict:
        print("❌ ElevenLabs の voice 一覧が取得できませんでした。APIキー/接続/権限を確認してください。")
        return None

    if voice is None:
        voice_id = _pick_default_voice_id(voices_dict)
    elif _is_voice_id(voice):
        voice_id = voice
    else:
        try:
            _, voice_id = resolve_speaker_id(str(voice), api_key=api_key, voices_dict=voices_dict)
        except Exception:
            voice_id = _pick_default_voice_id(voices_dict)
            print(f"  [warn] voice='{voice}' が見つからないためデフォルト voice_id={voice_id} を使用します")

    kwargs = {
        "text": text,
        "voice_id": voice_id,
        "model_id": model_id,
        "output_format": output_format,
        "voice_settings": _make_voice_settings(voice_settings),
    }

    if optimize_streaming_latency is not None:
        kwargs["optimize_streaming_latency"] = str(optimize_streaming_latency)

    if language_code is not None:
        kwargs["language_code"] = language_code

    try:
        audio = client.text_to_speech.convert(**kwargs)
    except Exception as e:
        print(f"❌ ElevenLabs TTS エラー: {e}")
        return None

    total = _write_audio_to_file(audio, outfile)

    if total <= 0:
        print("❌ Error: 音声データが空でした")
        return None

    if os.path.exists(outfile):
        print(f"    ** 一時ファイル [{outfile}] を保存しました")
        return outfile

    print(f"    ** Error: ファイル [{outfile}] の出力に失敗しました")
    return None


def speak_streaming(
    text,
    voice=None,
    api_key=None,
    model_id=DEFAULT_MODEL_ID,
    voice_settings=None,
    output_format=DEFAULT_OUTPUT_FORMAT,
    outfile=None,
    optimize_streaming_latency=None,
    language_code=None,
):
    client = _get_client(api_key)

    voices_dict = get_available_voices_info(api_key)
    if not voices_dict:
        raise RuntimeError("ElevenLabs の voice 一覧が取得できませんでした。")

    if voice is None:
        voice_id = _pick_default_voice_id(voices_dict)
    elif _is_voice_id(voice):
        voice_id = voice
    else:
        try:
            _, voice_id = resolve_speaker_id(str(voice), api_key=api_key, voices_dict=voices_dict)
        except Exception:
            voice_id = _pick_default_voice_id(voices_dict)

    kwargs = {
        "text": text,
        "voice_id": voice_id,
        "model_id": model_id,
        "output_format": output_format,
        "voice_settings": _make_voice_settings(voice_settings),
    }

    if optimize_streaming_latency is not None:
        kwargs["optimize_streaming_latency"] = str(optimize_streaming_latency)

    if language_code is not None:
        kwargs["language_code"] = language_code

    stream_iter = client.text_to_speech.stream(**kwargs)

    f = None
    try:
        if outfile:
            f = open(outfile, "wb")

        for chunk in stream_iter:
            if not chunk:
                continue
            if f:
                f.write(chunk)
            yield chunk

    finally:
        if f:
            f.close()


def speak_dialogue(
    dialogue,
    replacements,
    target_voices,
    speakers={},
    temp_dir=".",
    outfile=None,
    ext="mp3",
    api_key=None,
    cfg=None,
    model_id=DEFAULT_MODEL_ID,
    voice_settings=None,
    output_format=DEFAULT_OUTPUT_FORMAT,
):
    print("tktts_elevenlabs.speak_dialogue(): target_voices:", target_voices)

    tmpfiles = []
    voices_dict = None
    idx = 1

    for i, _dialogue in enumerate(dialogue):
        print()
        print(f"Dialogue {i:04d}:")

        dialogue_list = split_dialogue(
            _dialogue,
            target_voices,
            speakers=speakers,
            default_voice=DEFAULT_ELEVENLABS_VOICE,
            is_monologue=getattr(cfg, "monologue", False) if cfg else False,
        )

        for speaker, text in dialogue_list:
            tmpfile = os.path.join(temp_dir, f"tmp_{idx:03d}.{ext}")
            text = apply_replacements(text, replacements)

            if isinstance(target_voices, str):
                speaker = target_voices

            if voices_dict is None:
                voices_dict = get_available_voices_info(api_key)

            try:
                voices_dict, target_voice_id = resolve_speaker_id(
                    speaker,
                    api_key=api_key,
                    voices_dict=voices_dict,
                )
            except ValueError as e:
                print(e)
                target_voice_id = _pick_default_voice_id(voices_dict)
                print(
                    f"  [warn] speaker='{speaker}' が見つからないため "
                    f"デフォルト voice_id={target_voice_id} を使用します"
                )

            print(f"  {idx:04d}: voice={speaker} (id={target_voice_id}): ", end="")
            print(text)

            speed_scale = getattr(cfg, "fspeak_rate", None) if cfg else None
            pitch_scale = getattr(cfg, "fspeak_pitch", None) if cfg else None

            _outfile = speak(
                outfile=tmpfile,
                text=text,
                voice=target_voice_id,
                speak_rate=speed_scale,
                speak_pitch=pitch_scale,
                api_key=api_key,
                model_id=model_id,
                voice_settings=voice_settings,
                output_format=output_format,
            )

            if _outfile is None:
                return False, tmpfiles

            tmpfiles.append(tmpfile)
            idx += 1

    return True, tmpfiles


def speak_dialogue_streaming(
    dialogue,
    replacements,
    target_voices,
    speakers={},
    api_key=None,
    cfg=None,
    model_id=DEFAULT_MODEL_ID,
    voice_settings=None,
    output_format=DEFAULT_OUTPUT_FORMAT,
):
    voices_dict = None

    for _dialogue in dialogue:
        dialogue_list = split_dialogue(
            _dialogue,
            target_voices,
            speakers=speakers,
            default_voice=DEFAULT_ELEVENLABS_VOICE,
            is_monologue=getattr(cfg, "monologue", False) if cfg else False,
        )

        if voices_dict is None:
            voices_dict = get_available_voices_info(api_key)

        for speaker, text in dialogue_list:
            text = apply_replacements(text, replacements)

            if isinstance(target_voices, str):
                speaker = target_voices

            try:
                _, voice_id = resolve_speaker_id(
                    speaker,
                    api_key=api_key,
                    voices_dict=voices_dict,
                )
            except Exception:
                voice_id = _pick_default_voice_id(voices_dict)

            for chunk in speak_streaming(
                text=text,
                voice=voice_id,
                api_key=api_key,
                model_id=model_id,
                voice_settings=voice_settings,
                output_format=output_format,
                outfile=None,
            ):
                yield speaker, text, chunk


if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(description="ElevenLabs voice list utility")
    parser.add_argument("--api_key", type=str, default=DEFAULT_API_KEY, help="ElevenLabs API key")
    parser.add_argument("--mode", type=str, default="list", choices=["list", "names"],
                        help="list: 詳細表示, names: 名前のみ")

    args = parser.parse_args()

    print("=== ElevenLabs Voice List ===")

    try:
        if args.mode == "list":
            ok = list_available_voices(api_key=args.api_key)
            if not ok:
                print("No voices found.")
        else:
            voices = get_available_voices(api_key=args.api_key)
            if not voices:
                print("No voices found.")
            else:
                for v in voices:
                    print(v)

    except Exception as e:
        print(f"❌ Error: {e}")
        import traceback
        traceback.print_exc()

    speak(
        outfile="ja_test.mp3",
        text = "いいログです。「状況がはっきり見えました。結論から言うと",
        voice="hpp4J3VqNfWAUOO0d1Us",  # Bella
        model_id="eleven_multilingual_v2",
        language_code="ja",
        voice_settings={"speed": 0.8}
    )

    input("\nPress ENTER to terminate>>\n")