"""
Test multilingual support for retain and reflect operations.

Tests that the system correctly handles non-English input and produces
output in the same language as the input.
"""

import pytest
import logging
from datetime import datetime, timezone
from hindsight_api.engine.memory_engine import Budget
from hindsight_api import RequestContext

logger = logging.getLogger(__name__)


@pytest.mark.asyncio
@pytest.mark.xfail(
    strict=False,
    reason="Gemini sometimes consistently translates Chinese content to English despite instructions",
)
async def test_retain_chinese_content(memory, request_context):
    """
    Test that retain correctly extracts facts from Chinese content
    and keeps the output in Chinese.

    This test verifies:
    1. Facts are extracted from Chinese text
    2. The extracted facts contain Chinese characters
    3. Entity names are preserved in Chinese

    Note: LLM fact extraction is non-deterministic and may sometimes translate
    content to English despite instructions. We retry up to 3 times.
    """
    max_retries = 3
    last_error = None

    for attempt in range(max_retries):
        bank_id = f"test_chinese_retain_{datetime.now(timezone.utc).timestamp()}_{attempt}"

        try:
            # Chinese content about a person and their activities
            chinese_content = """
            张伟是一位资深软件工程师，在腾讯工作了五年。他专门研究分布式系统，
            并领导了公司微服务架构的开发。他以编写干净、文档完善的代码而闻名。

            李明上个月加入团队担任初级开发人员。他正在学习React和Node.js。
            李明很有热情，在代码审查中提出很好的问题。他最近完成了他的第一个功能，
            这是一个用户认证流程。

            团队使用Kubernetes进行容器编排，并部署到阿里云。他们遵循敏捷方法论，
            采用两周冲刺周期。合并前必须进行代码审查。
            """

            # Retain the Chinese content
            unit_ids = await memory.retain_async(
                bank_id=bank_id,
                content=chinese_content,
                context="团队概述",  # Chinese context
                event_date=datetime(2024, 1, 15, tzinfo=timezone.utc),
                request_context=request_context,
            )

            logger.info(f"Retained {len(unit_ids)} facts from Chinese content (attempt {attempt + 1})")
            assert len(unit_ids) > 0, "Should have extracted and stored facts from Chinese content"

            # Recall the facts with a Chinese query
            result = await memory.recall_async(
                bank_id=bank_id,
                query="告诉我关于张伟的信息",  # "Tell me about Zhang Wei"
                budget=Budget.MID,
                max_tokens=1000,
                fact_type=["world"],
                request_context=request_context,
            )

            logger.info(f"Recalled {len(result.results)} facts")
            assert len(result.results) > 0, "Should recall facts about Zhang Wei"

            # Verify that the facts contain Chinese characters
            # At least one fact should mention 张伟 (Zhang Wei) or related Chinese content
            chinese_facts_found = 0
            for fact in result.results:
                logger.info(f"Fact: {fact.text[:100]}...")
                # Check for common Chinese characters or the name
                if any(
                    char in fact.text
                    for char in ["张", "伟", "腾讯", "软件", "工程师", "分布式", "系统", "代码"]
                ):
                    chinese_facts_found += 1

            logger.info(f"Found {chinese_facts_found} facts with Chinese content")
            assert chinese_facts_found > 0, (
                f"Expected facts to contain Chinese characters, but none found. "
                f"Facts: {[f.text for f in result.results]}"
            )

            logger.info("Chinese retain test passed - facts preserved in Chinese")
            return  # Test passed

        except AssertionError as e:
            last_error = e
            if attempt < max_retries - 1:
                logger.warning(f"Attempt {attempt + 1} failed: {e}. Retrying...")
            else:
                raise e
        finally:
            try:
                await memory.delete_bank(bank_id, request_context=request_context)
            except Exception:
                pass


@pytest.mark.asyncio
async def test_reflect_chinese_content(memory, request_context):
    """
    Test that reflect correctly generates responses in Chinese
    when given Chinese facts and a Chinese query.

    This test verifies:
    1. Reflection produces a response in Chinese
    2. The response references the Chinese facts
    3. Opinions are formed and expressed in Chinese

    Note: LLM responses are non-deterministic, so we retry up to 3 times
    to account for occasional hallucinations of different names.
    """
    bank_id = f"test_chinese_reflect_{datetime.now(timezone.utc).timestamp()}"
    max_retries = 3

    try:
        # Store some Chinese facts to give context for opinion formation
        await memory.retain_async(
            bank_id=bank_id,
            content="张伟是一位优秀的软件工程师，完成了五个重大项目。他总是按时交付，代码整洁有良好的文档。",
            context="绩效评估",  # "Performance review"
            event_date=datetime(2024, 1, 15, tzinfo=timezone.utc),
            request_context=request_context,
        )

        await memory.retain_async(
            bank_id=bank_id,
            content="李明最近加入团队。他错过了第一个截止日期，代码有很多bug。",
            context="绩效评估",
            event_date=datetime(2024, 2, 1, tzinfo=timezone.utc),
            request_context=request_context,
        )

        last_error = None
        for attempt in range(max_retries):
            try:
                # Reflect with a Chinese query
                query = "谁是更可靠的工程师？"  # "Who is a more reliable engineer?"
                result = await memory.reflect_async(
                    bank_id=bank_id,
                    query=query,
                    budget=Budget.MID,
                    request_context=request_context,
                )

                logger.info(f"Reflection answer (attempt {attempt + 1}): {result.text}")

                # Verify we got an answer
                assert result.text, "Reflection should return an answer"

                # Check that the response contains Chinese characters
                # The response should be in Chinese, not English
                chinese_chars_found = sum(1 for char in result.text if "\u4e00" <= char <= "\u9fff")
                total_chars = len(result.text.replace(" ", "").replace("\n", ""))

                logger.info(f"Chinese characters: {chinese_chars_found}, Total characters: {total_chars}")

                # At least 30% of characters should be Chinese (allowing for numbers, punctuation)
                chinese_ratio = chinese_chars_found / max(total_chars, 1)
                assert chinese_ratio > 0.3, (
                    f"Expected response to be in Chinese (>30% Chinese characters), "
                    f"but only {chinese_ratio:.1%} are Chinese. Response: {result.text}"
                )

                # Check that Chinese names are mentioned
                # The LLM should use names from the based_on facts, not hallucinate different names
                # Extract Chinese names from the based_on world facts
                expected_names = set()
                for fact in result.based_on.get("world", []):
                    # Extract Chinese entity names from the fact
                    for entity in (fact.entities or []):
                        # Check if entity contains Chinese characters
                        if any("\u4e00" <= char <= "\u9fff" for char in entity):
                            expected_names.add(entity)

                # Also check for the specific names we stored
                expected_names.update(["张伟", "李明"])

                # At least one expected name should appear in the response
                found_name = any(name in result.text for name in expected_names)
                assert found_name, (
                    f"Expected response to mention one of the Chinese names: {expected_names}. Response: {result.text}"
                )

                logger.info("Chinese reflect test passed - response generated in Chinese")
                return  # Test passed, exit

            except AssertionError as e:
                last_error = e
                if attempt < max_retries - 1:
                    logger.warning(f"Attempt {attempt + 1} failed: {e}. Retrying...")
                    continue
                else:
                    raise e

    finally:
        await memory.delete_bank(bank_id, request_context=request_context)


@pytest.mark.asyncio
async def test_retain_japanese_content(memory, request_context):
    """
    Test that retain correctly handles Japanese content.

    This test verifies multilingual support extends beyond Chinese
    to other non-Latin languages.

    Note: LLM fact extraction is non-deterministic and may sometimes translate
    content to English despite instructions. We retry up to 3 times.
    """
    max_retries = 3
    last_error = None

    for attempt in range(max_retries):
        # Use unique bank_id per attempt to avoid stale data
        bank_id = f"test_japanese_retain_{datetime.now(timezone.utc).timestamp()}_{attempt}"

        try:
            # Japanese content about a developer
            japanese_content = """
            田中さんはソフトウェアエンジニアで、東京のスタートアップで働いています。
            彼女はPythonとTypeScriptが得意で、毎日コードレビューをしています。
            先週、新しいAPIを完成させました。
            """

            unit_ids = await memory.retain_async(
                bank_id=bank_id,
                content=japanese_content,
                context="チームプロフィール",  # "Team profile"
                event_date=datetime(2024, 1, 15, tzinfo=timezone.utc),
                request_context=request_context,
            )

            logger.info(f"Retained {len(unit_ids)} facts from Japanese content (attempt {attempt + 1})")
            assert len(unit_ids) > 0, "Should have extracted facts from Japanese content"

            # Recall with Japanese query
            result = await memory.recall_async(
                bank_id=bank_id,
                query="田中さんについて教えてください",  # "Tell me about Tanaka-san"
                budget=Budget.MID,
                max_tokens=1000,
                fact_type=["world"],
                request_context=request_context,
            )

            assert len(result.results) > 0, "Should recall facts about Tanaka"

            # Check for Japanese content in facts
            japanese_facts_found = 0
            for fact in result.results:
                logger.info(f"Fact: {fact.text[:100]}...")
                # Check for Japanese characters (hiragana, katakana, or kanji)
                if any(
                    ("\u3040" <= char <= "\u309f")  # Hiragana
                    or ("\u30a0" <= char <= "\u30ff")  # Katakana
                    or ("\u4e00" <= char <= "\u9fff")  # Kanji
                    for char in fact.text
                ):
                    japanese_facts_found += 1

            assert japanese_facts_found > 0, (
                f"Expected facts to contain Japanese characters. "
                f"Facts: {[f.text for f in result.results]}"
            )

            logger.info("Japanese retain test passed - facts preserved in Japanese")
            return  # Test passed

        except AssertionError as e:
            last_error = e
            if attempt < max_retries - 1:
                logger.warning(f"Attempt {attempt + 1} failed: {e}. Retrying...")
            else:
                raise e
        finally:
            # Cleanup the bank
            try:
                await memory.delete_bank(bank_id, request_context=request_context)
            except Exception:
                pass


@pytest.mark.asyncio
async def test_english_content_stays_english(memory, request_context):
    """
    Test that English content is NOT incorrectly translated to Japanese or Chinese.

    This test specifically catches the bug where the language instruction in the
    CONCISE extraction prompt mentioned Japanese/Chinese explicitly, which primed
    the LLM to sometimes output facts in those languages even for English input.

    See: https://github.com/vectorize-io/hindsight/issues/181
    """
    bank_id = f"test_english_retain_{datetime.now(timezone.utc).timestamp()}"

    try:
        # English content about a developer
        english_content = """
        John Smith is a software engineer at TechCorp in Seattle.
        He specializes in machine learning and has been working on
        recommendation systems for the past three years.
        Last month, he launched a new feature that improved click-through rates by 25%.
        He prefers working in Python and uses PyTorch for model training.
        """

        unit_ids = await memory.retain_async(
            bank_id=bank_id,
            content=english_content,
            context="Team profile",
            event_date=datetime(2024, 1, 15, tzinfo=timezone.utc),
            request_context=request_context,
        )

        logger.info(f"Retained {len(unit_ids)} facts from English content")
        assert len(unit_ids) > 0, "Should have extracted facts from English content"

        # Recall with English query
        result = await memory.recall_async(
            bank_id=bank_id,
            query="Tell me about John Smith",
            budget=Budget.MID,
            max_tokens=1000,
            fact_type=["world"],
            request_context=request_context,
        )

        assert len(result.results) > 0, "Should recall facts about John Smith"

        # Verify facts are NOT in Japanese or Chinese
        for fact in result.results:
            logger.info(f"Fact: {fact.text}")

            # Count Japanese characters (hiragana, katakana)
            japanese_chars = sum(
                1 for char in fact.text
                if ("\u3040" <= char <= "\u309f") or ("\u30a0" <= char <= "\u30ff")
            )

            # Count Chinese/CJK characters (excluding those also used in Japanese)
            # Note: Kanji/CJK ideographs overlap between Chinese and Japanese
            cjk_chars = sum(1 for char in fact.text if "\u4e00" <= char <= "\u9fff")

            # For English input, there should be minimal CJK characters
            # Allow for occasional edge cases (e.g., proper nouns) but not full translation
            total_chars = len(fact.text)
            cjk_ratio = cjk_chars / max(total_chars, 1)

            assert cjk_ratio < 0.1, (
                f"English content was incorrectly translated to CJK language! "
                f"CJK ratio: {cjk_ratio:.1%}, Japanese chars: {japanese_chars}, CJK chars: {cjk_chars}. "
                f"Fact: {fact.text}"
            )

        logger.info("English content test passed - facts stayed in English")

    finally:
        await memory.delete_bank(bank_id, request_context=request_context)


@pytest.mark.asyncio
async def test_italian_content_stays_italian(memory, request_context):
    """
    Test that Italian content is NOT incorrectly translated to Japanese or Chinese.

    Similar to the English test, this catches the bug where non-CJK languages
    could be incorrectly translated due to biased language instruction.

    See: https://github.com/vectorize-io/hindsight/issues/181
    """
    bank_id = f"test_italian_retain_{datetime.now(timezone.utc).timestamp()}"

    try:
        # Italian content about a chef
        italian_content = """
        Marco Rossi è uno chef italiano che lavora in un ristorante a Milano.
        È specializzato nella cucina toscana e ha vinto tre premi gastronomici.
        Il mese scorso ha aperto un nuovo ristorante nel centro della città.
        Preferisce usare ingredienti freschi e locali per i suoi piatti.
        """

        unit_ids = await memory.retain_async(
            bank_id=bank_id,
            content=italian_content,
            context="Profilo dello chef",
            event_date=datetime(2024, 1, 15, tzinfo=timezone.utc),
            request_context=request_context,
        )

        logger.info(f"Retained {len(unit_ids)} facts from Italian content")
        assert len(unit_ids) > 0, "Should have extracted facts from Italian content"

        # Recall with Italian query
        result = await memory.recall_async(
            bank_id=bank_id,
            query="Dimmi di Marco Rossi",  # "Tell me about Marco Rossi"
            budget=Budget.MID,
            max_tokens=1000,
            fact_type=["world"],
            request_context=request_context,
        )

        assert len(result.results) > 0, "Should recall facts about Marco Rossi"

        # Verify facts are NOT in Japanese or Chinese - should stay in Italian
        for fact in result.results:
            logger.info(f"Fact: {fact.text}")

            # Count CJK characters
            cjk_chars = sum(1 for char in fact.text if "\u4e00" <= char <= "\u9fff")
            japanese_chars = sum(
                1 for char in fact.text
                if ("\u3040" <= char <= "\u309f") or ("\u30a0" <= char <= "\u30ff")
            )

            total_chars = len(fact.text)
            cjk_ratio = (cjk_chars + japanese_chars) / max(total_chars, 1)

            assert cjk_ratio < 0.1, (
                f"Italian content was incorrectly translated to CJK language! "
                f"CJK ratio: {cjk_ratio:.1%}. Fact: {fact.text}"
            )

        # Verify facts contain Italian words (basic sanity check)
        all_text = " ".join(f.text for f in result.results).lower()
        italian_indicators = ["marco", "rossi", "chef", "ristorante", "milano", "cucina", "italiano", "italiana"]
        has_italian = any(word in all_text for word in italian_indicators)

        # Allow English translation as acceptable (not ideal but not the bug)
        english_indicators = ["chef", "restaurant", "milan", "italian", "cooking"]
        has_english = any(word in all_text for word in english_indicators)

        assert has_italian or has_english, (
            f"Expected facts to be in Italian or English, but got neither. Facts: {all_text}"
        )

        logger.info("Italian content test passed - facts not translated to CJK")

    finally:
        await memory.delete_bank(bank_id, request_context=request_context)


@pytest.mark.asyncio
async def test_mixed_language_entities(memory, request_context):
    """
    Test that entity extraction works correctly with mixed language content.

    Some entities (like company names) might be in English while the
    description is in Chinese.
    """
    bank_id = f"test_mixed_lang_{datetime.now(timezone.utc).timestamp()}"

    try:
        # Mixed language content - Chinese with English company names
        mixed_content = """
        王芳在Google北京办公室工作，她是一名高级产品经理。
        之前她在Microsoft和Amazon工作过。
        她负责管理YouTube在中国市场的推广策略。
        """

        unit_ids = await memory.retain_async(
            bank_id=bank_id,
            content=mixed_content,
            context="员工资料",
            event_date=datetime(2024, 1, 15, tzinfo=timezone.utc),
            request_context=request_context,
        )

        assert len(unit_ids) > 0, "Should extract facts from mixed language content"

        # Recall and check entities
        result = await memory.recall_async(
            bank_id=bank_id,
            query="王芳在哪里工作？",  # "Where does Wang Fang work?"
            budget=Budget.MID,
            max_tokens=1000,
            fact_type=["world"],
            request_context=request_context,
        )

        assert len(result.results) > 0, "Should recall facts about Wang Fang"

        # Check that both Chinese and English entities are preserved
        all_text = " ".join(f.text for f in result.results)
        logger.info(f"Combined facts: {all_text}")

        # Should contain Chinese name and/or English company names
        has_chinese_name = "王芳" in all_text
        has_english_company = any(
            company in all_text for company in ["Google", "Microsoft", "Amazon", "YouTube"]
        )

        assert has_chinese_name or has_english_company, (
            f"Expected mixed language entities. Facts: {all_text}"
        )

        logger.info("Mixed language entity test passed")

    finally:
        await memory.delete_bank(bank_id, request_context=request_context)
