"""test_multilang.py -- Unit tests for Issue #167 multi-language support."""

from __future__ import annotations
import json, os
from typing import Any, Dict, Optional
import pytest

def _pkg_root():
    return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

def _read_src(rel_path):
    with open(os.path.join(_pkg_root(), rel_path)) as f:
        return f.read()

def _extract_lang_names():
    import ast
    src = _read_src("saltybot_social/conversation_node.py")
    start = src.index("_LANG_NAMES: Dict[str, str] = {")
    end   = src.index("\n}", start) + 2
    return ast.literal_eval(src[start:end].split("=",1)[1].strip())

class TestLangNames:
    @pytest.fixture(scope="class")
    def ln(self): return _extract_lang_names()
    def test_english(self, ln): assert ln["en"] == "English"
    def test_french(self, ln):  assert ln["fr"] == "French"
    def test_spanish(self, ln): assert ln["es"] == "Spanish"
    def test_german(self, ln):  assert ln["de"] == "German"
    def test_japanese(self, ln):assert ln["ja"] == "Japanese"
    def test_chinese(self, ln): assert ln["zh"] == "Chinese"
    def test_arabic(self, ln):  assert ln["ar"] == "Arabic"
    def test_at_least_15(self, ln): assert len(ln) >= 15
    def test_lowercase_keys(self, ln):
        for k in ln: assert k == k.lower() and 2 <= len(k) <= 3
    def test_nonempty_values(self, ln):
        for k, v in ln.items(): assert v

class TestLanguageHint:
    def _h(self, sl, sid, ln):
        lang = sl.get(sid, "en")
        return f"[Please respond in {ln.get(lang, lang)}.]" if lang and lang != "en" else ""
    @pytest.fixture(scope="class")
    def ln(self): return _extract_lang_names()
    def test_english_no_hint(self, ln): assert self._h({"p": "en"}, "p", ln) == ""
    def test_unknown_no_hint(self, ln): assert self._h({}, "p", ln) == ""
    def test_french(self, ln): assert self._h({"p":"fr"},"p",ln) == "[Please respond in French.]"
    def test_spanish(self, ln): assert self._h({"p":"es"},"p",ln) == "[Please respond in Spanish.]"
    def test_unknown_code(self, ln): assert "xx" in self._h({"p":"xx"},"p",ln)
    def test_brackets(self, ln):
        h = self._h({"p":"de"},"p",ln)
        assert h.startswith("[") and h.endswith("]")

class TestVoiceMap:
    def _parse(self, jstr, dl, dp):
        try: extra = json.loads(jstr) if jstr.strip() not in ("{}","") else {}
        except: extra = {}
        r = {dl: dp}; r.update(extra); return r
    def test_empty(self): assert self._parse("{}","en","/e") == {"en":"/e"}
    def test_extra(self):
        vm = self._parse('{"fr":"/f"}', "en", "/e")
        assert vm["fr"] == "/f"
    def test_invalid(self): assert self._parse("BAD","en","/e") == {"en":"/e"}
    def test_multi(self):
        assert len(self._parse(json.dumps({"fr":"/f","es":"/s"}),"en","/e")) == 3

class TestVoiceSelect:
    def _s(self, voices, lang, default):
        return voices.get(lang) or voices.get(default)
    def test_exact(self): assert self._s({"en":"E","fr":"F"},"fr","en") == "F"
    def test_fallback(self): assert self._s({"en":"E"},"fr","en") == "E"
    def test_none(self): assert self._s({},"fr","en") is None

class TestSttFields:
    @pytest.fixture(scope="class")
    def src(self): return _read_src("saltybot_social/speech_pipeline_node.py")
    def test_param(self, src): assert "whisper_language" in src
    def test_detected_lang(self, src): assert "detected_lang" in src
    def test_msg_language(self, src): assert "msg.language = language" in src
    def test_auto_detect(self, src): assert "language=self._whisper_language" in src

class TestConvFields:
    @pytest.fixture(scope="class")
    def src(self): return _read_src("saltybot_social/conversation_node.py")
    def test_speaker_lang(self, src): assert "_speaker_lang" in src
    def test_lang_hint_method(self, src): assert "_language_hint" in src
    def test_msg_language(self, src): assert "msg.language = language" in src
    def test_lang_names(self, src): assert "_LANG_NAMES" in src
    def test_please_respond(self, src): assert "Please respond in" in src
    def test_emotion_coexists(self, src): assert "_emotion_hint" in src

class TestTtsFields:
    @pytest.fixture(scope="class")
    def src(self): return _read_src("saltybot_social/tts_node.py")
    def test_voice_map_json(self, src): assert "voice_map_json" in src
    def test_default_lang(self, src): assert "default_language" in src
    def test_voices_dict(self, src): assert "_voices" in src
    def test_get_voice(self, src): assert "_get_voice" in src
    def test_load_voice_for_lang(self, src): assert "_load_voice_for_lang" in src
    def test_queue_tuple(self, src): assert "(text.strip(), lang)" in src
    def test_synthesize_voice_arg(self, src): assert "_synthesize(text, voice)" in src

class TestMsgDefs:
    @pytest.fixture(scope="class")
    def tr(self): return _read_src("../saltybot_social_msgs/msg/SpeechTranscript.msg")
    @pytest.fixture(scope="class")
    def re(self): return _read_src("../saltybot_social_msgs/msg/ConversationResponse.msg")
    def test_transcript_lang(self, tr): assert "string  language" in tr
    def test_transcript_bcp47(self, tr): assert "BCP-47" in tr
    def test_response_lang(self, re): assert "string  language" in re

class TestEdgeCases:
    def test_empty_lang_no_hint(self):
        lang = "" or "en"; assert lang == "en"
    def test_lang_flows(self):
        d: Dict[str,str] = {}; d["p1"] = "fr"
        assert d.get("p1","en") == "fr"
    def test_multi_speakers(self):
        d = {"p1":"fr","p2":"es"}
        assert d["p1"] == "fr" and d["p2"] == "es"
    def test_voice_map_code_in_tts(self):
        src = _read_src("saltybot_social/tts_node.py")
        assert "voice_map_json" in src and "json.loads" in src
