"""
Tests for the truncation detection utility.
"""

import pytest

from src.utils import detect_truncation


class TestCodeLevelTruncation:
    """Test detection of code-level truncation markers."""

    def test_truncated_marker_detected(self):
        """TRUNCATED marker should be detected as code truncation."""
        text = "Some content\n[...TRUNCATED 5000 chars...]\nMore content"
        result = detect_truncation(text)
        assert result["truncated"] is True
        assert result["source"] == "code"
        assert result["confidence"] == "high"

    def test_truncated_lowercase_detected(self):
        """Lowercase truncated marker should also be detected."""
        text = "Content here\n[...truncated]\nEnd"
        result = detect_truncation(text)
        assert result["truncated"] is True
        assert result["source"] == "code"

    def test_note_data_truncated_detected(self):
        """NOTE: Data truncated marker should be detected."""
        text = "Data here\n[NOTE: Data truncated due to size limits]\nMore"
        result = detect_truncation(text)
        assert result["truncated"] is True
        assert result["source"] == "code"


class TestLLMTruncation:
    """Test detection of LLM-level truncation heuristics."""

    def test_ends_mid_sentence(self):
        """Text ending mid-sentence should be flagged as LLM truncation."""
        text = "The company's revenue grew by 15% driven by"
        result = detect_truncation(text)
        assert result["truncated"] is True
        assert result["source"] == "llm"
        assert result["confidence"] == "medium"

    def test_ends_mid_word(self):
        """Text ending mid-word should be flagged."""
        text = "The analysis shows that the comp"
        result = detect_truncation(text)
        assert result["truncated"] is True
        assert result["source"] == "llm"

    def test_incomplete_pm_block(self):
        """PM_BLOCK without VERDICT should be flagged."""
        text = "PM_BLOCK:\nTICKER: TEST.X\nCONVICTION: 75%"
        result = detect_truncation(text)
        assert result["truncated"] is True
        assert result["source"] == "llm"
        assert "PM_BLOCK" in result["marker"]

    def test_incomplete_data_block(self):
        """DATA_BLOCK without scores should be flagged."""
        text = "DATA_BLOCK:\nTICKER: TEST.X\nSECTOR: Technology"
        result = detect_truncation(text)
        assert result["truncated"] is True
        assert "DATA_BLOCK" in result["marker"]


class TestCompleteOutput:
    """Test that complete outputs are not falsely flagged."""

    def test_complete_sentence(self):
        """Complete sentence should not be flagged."""
        text = "Analysis complete. VERDICT: BUY with 75% conviction."
        result = detect_truncation(text)
        assert result["truncated"] is False

    def test_complete_pm_block(self):
        """Complete PM_BLOCK should not be flagged."""
        text = "PM_BLOCK:\nTICKER: TEST.X\nVERDICT: BUY\nRISK_ZONE: LOW"
        result = detect_truncation(text)
        assert result["truncated"] is False

    def test_complete_markdown_pm_block_with_zone_not_flagged(self):
        """Real emitted PM block format should not trigger a false positive."""
        text = (
            "#### --- START PM_BLOCK ---\n"
            "VERDICT: BUY\n"
            "ZONE: LOW\n"
            "TARGET: 10%\n"
            "#### --- END PM_BLOCK ---"
        )
        result = detect_truncation(text, agent="portfolio_manager")
        assert result["truncated"] is False

    def test_complete_legacy_pm_block_not_flagged(self):
        """Legacy PM_BLOCK header with required fields should be accepted."""
        text = "PM_BLOCK:\nVERDICT: BUY\nRISK_ZONE: LOW"
        result = detect_truncation(text, agent="portfolio_manager")
        assert result["truncated"] is False

    def test_complete_data_block(self):
        """Complete DATA_BLOCK should not be flagged."""
        text = "DATA_BLOCK:\nHEALTH_SCORE: 75\nGROWTH_SCORE: 60"
        result = detect_truncation(text)
        assert result["truncated"] is False

    def test_complete_fenced_data_block_not_flagged_for_fundamentals_agent(self):
        """Fenced DATA_BLOCK should short-circuit truncation heuristics."""
        text = (
            "### --- START DATA_BLOCK ---\n"
            "HEALTH_SCORE: 75\n"
            "GROWTH_SCORE: 60\n"
            "### --- END DATA_BLOCK ---"
        )
        result = detect_truncation(text, agent="fundamentals_analyst")
        assert result["truncated"] is False

    def test_complete_fenced_value_trap_block_not_flagged(self):
        """Fenced VALUE_TRAP_BLOCK should not be flagged as truncated."""
        text = (
            "### --- START VALUE_TRAP_BLOCK ---\n"
            "SCORE: 45\n"
            "VERDICT: CAUTIOUS\n"
            "### --- END VALUE_TRAP_BLOCK ---"
        )
        result = detect_truncation(text, agent="value_trap_detector")
        assert result["truncated"] is False

    def test_complete_legacy_forensic_block_not_flagged(self):
        """Legacy FORENSIC_DATA_BLOCK should remain accepted."""
        text = "FORENSIC_DATA_BLOCK:\nSTATUS: CLEAN\nVERDICT: RELY_ON_DATA_BLOCK"
        result = detect_truncation(text, agent="global_forensic_auditor")
        assert result["truncated"] is False

    def test_raw_data_exact_end_sentinel_not_flagged_for_junior_fundamentals(self):
        """Junior raw-data sentinel should count as a complete structured terminator."""
        text = (
            "=== RAW FINANCIAL DATA FOR TEST ===\n"
            "### TOOL 1: get_financial_metrics\n"
            '{"currency": "USD"}\n'
            "### TOOL 2: get_fundamental_analysis\n"
            "Coverage looks limited.\n"
            "=== END RAW DATA ==="
        )
        result = detect_truncation(text, agent="junior_fundamentals_analyst")
        assert result["truncated"] is False

    def test_ends_with_period(self):
        """Text ending with period should not be flagged."""
        text = "The investment thesis is sound."
        result = detect_truncation(text)
        assert result["truncated"] is False

    def test_ends_with_quote(self):
        """Text ending with quote should not be flagged."""
        text = 'The CEO said "We are optimistic"'
        result = detect_truncation(text)
        assert result["truncated"] is False

    def test_ends_with_newline(self):
        """Text ending with newline should not be flagged."""
        text = "Analysis complete.\n"
        result = detect_truncation(text)
        assert result["truncated"] is False

    def test_ends_with_smart_double_quote(self):
        """GPT-style smart right double quote should not be flagged."""
        text = "I would not stake my reputation on without fixes.\u201d"
        result = detect_truncation(text)
        assert result["truncated"] is False

    def test_ends_with_smart_single_quote(self):
        """Smart right single quote should not be flagged."""
        text = "The CEO\u2019s outlook is positive.\u2019"
        result = detect_truncation(text)
        assert result["truncated"] is False

    def test_ends_with_cjk_period(self):
        """CJK period (Japanese/Chinese) should not be flagged."""
        text = "分析が完了しました\u3002"
        result = detect_truncation(text)
        assert result["truncated"] is False

    def test_ends_with_cjk_bracket(self):
        """CJK right corner bracket should not be flagged."""
        text = "\u300c分析完了\u300d"
        result = detect_truncation(text)
        assert result["truncated"] is False

    def test_ends_with_ellipsis_char(self):
        """Unicode horizontal ellipsis should not be flagged."""
        text = "The outlook remains uncertain\u2026"
        result = detect_truncation(text)
        assert result["truncated"] is False

    def test_terminal_field_na_not_flagged(self):
        text = "OPPORTUNITY: N/A"
        result = detect_truncation(text)
        assert result["truncated"] is False

    def test_terminal_field_verdict_not_flagged(self):
        text = "VERDICT: BUY"
        result = detect_truncation(text)
        assert result["truncated"] is False

    def test_terminal_field_status_not_flagged(self):
        text = "STATUS: CLEAN"
        result = detect_truncation(text)
        assert result["truncated"] is False

    def test_terminal_field_detail_na_not_flagged(self):
        text = "DETAIL: N/A"
        result = detect_truncation(text)
        assert result["truncated"] is False

    def test_complete_tavily_wrapper_with_truncation_marker_not_flagged(self):
        text = (
            '<search_results source="tavily" data_type="external_web_content">\n'
            "<result><summary>Example</summary></result>\n"
            "[...truncated]\n"
            "</search_results>"
        )
        result = detect_truncation(text)
        assert result["truncated"] is False

    def test_complete_tavily_wrapper_with_mid_result_marker_not_flagged(self):
        text = (
            '<search_results source="tavily" data_type="external_web_content">\n'
            "<result><summary>Example</summary></result>\n"
            "[...truncated mid-result]\n"
            "</search_results>"
        )
        result = detect_truncation(text)
        assert result["truncated"] is False

    def test_complete_junior_raw_wrapper_with_embedded_tavily_truncation_not_flagged(
        self,
    ):
        text = (
            "=== RAW FINANCIAL DATA FOR TEST ===\n\n"
            "### TOOL 1: get_financial_metrics\n"
            '{"currency": "USD"}\n\n'
            "### TOOL 2: get_fundamental_analysis\n"
            '<search_results source="tavily" data_type="external_web_content">\n'
            "<result><summary>Example</summary></result>\n"
            "[...truncated]\n"
            "</search_results>\n\n"
            "=== END RAW DATA ==="
        )
        result = detect_truncation(text, agent="junior_fundamentals_analyst")
        assert result["truncated"] is False


class TestEdgeCases:
    """Test edge cases and boundary conditions."""

    def test_empty_string(self):
        """Empty string should not be flagged as truncated."""
        result = detect_truncation("")
        assert result["truncated"] is False

    def test_none_input(self):
        """None input should not crash."""
        result = detect_truncation(None)
        assert result["truncated"] is False

    def test_whitespace_only(self):
        """Whitespace-only input should not be flagged."""
        result = detect_truncation("   \n\t  ")
        assert result["truncated"] is False

    def test_very_short_text(self):
        """Very short text should still work."""
        result = detect_truncation("OK.")
        assert result["truncated"] is False

    def test_non_string_input(self):
        """Non-string input should not crash."""
        result = detect_truncation(12345)
        assert result["truncated"] is False


class TestAgentScoping:
    """Test that block checks are scoped to the producing agent."""

    def test_consultant_mentioning_data_block_not_flagged(self):
        """Consultant referencing DATA_BLOCK in prose should not trigger false positive."""
        text = (
            "CONSULTANT REVIEW: CONDITIONAL APPROVAL\n"
            "The DATA_BLOCK: shows P/E of 10.3 which is consistent with filings.\n"
            "Overall analysis is sound."
        )
        result = detect_truncation(text, agent="consultant")
        assert result["truncated"] is False

    def test_fundamentals_analyst_incomplete_data_block_flagged(self):
        """Fundamentals analyst with incomplete DATA_BLOCK should still be caught."""
        text = "DATA_BLOCK:\nTICKER: TEST.X\nSECTOR: Technology"
        result = detect_truncation(text, agent="fundamentals_analyst")
        assert result["truncated"] is True
        assert "DATA_BLOCK" in result["marker"]

    def test_no_agent_checks_all_blocks(self):
        """Without agent param, all block checks apply (backward compat)."""
        text = "DATA_BLOCK:\nTICKER: TEST.X\nSECTOR: Technology"
        result = detect_truncation(text)
        assert result["truncated"] is True

    def test_narrative_datablock_mention_does_not_count_as_complete_block(self):
        """Narrative mentions should not be treated as a real DATA_BLOCK start."""
        text = (
            "The DATA_BLOCK: shows strong margins in the report, but the summary ends"
        )
        result = detect_truncation(text, agent="fundamentals_analyst")
        assert result["truncated"] is True

    def test_terminal_field_with_dangling_phrase_is_flagged(self):
        text = "OPPORTUNITY: Benefiting from"
        result = detect_truncation(text)
        assert result["truncated"] is True

    def test_terminal_field_with_incomplete_detail_is_flagged(self):
        text = "DETAIL: Exposure to demand in"
        result = detect_truncation(text)
        assert result["truncated"] is True

    def test_incomplete_junior_raw_wrapper_is_flagged(self):
        text = (
            "=== RAW FINANCIAL DATA FOR TEST ===\n\n"
            "### TOOL 1: get_financial_metrics\n"
            '{"currency": "USD"}\n\n'
            "### TOOL 2: get_fundamental_analysis\n"
            '<search_results source="tavily" data_type="external_web_content">\n'
            "<result><summary>Example</summary></result>\n"
            "[...truncated]\n"
            "</search_results>"
        )
        result = detect_truncation(text, agent="junior_fundamentals_analyst")
        assert result["truncated"] is True
        assert "RAW DATA wrapper" in result["marker"]

    def test_plain_code_truncation_marker_outside_wrapper_still_flagged(self):
        text = "Some content\n[...TRUNCATED 5000 chars...]\nMore content"
        result = detect_truncation(text)
        assert result["truncated"] is True
        assert result["source"] == "code"

    def test_consultant_style_code_truncation_marker_outside_wrapper_still_flagged(
        self,
    ):
        text = (
            "CONSULTANT REVIEW\n"
            "Important content\n"
            "[...TRUNCATED 5000 chars...]\n"
            "[NOTE: Data truncated due to size limits. Partial analysis may still be useful.]"
        )
        result = detect_truncation(text, agent="consultant")
        assert result["truncated"] is True
        assert result["source"] == "code"
