"""Tests for truncate_output() and apply_output_limit() input validation."""

from __future__ import annotations

import json
import re

from godotiq.tools.output_limit import apply_output_limit, truncate_output


class TestTruncateOutput:
    """Tests for the truncate_output() function."""

    def test_short_text_brief_unchanged(self) -> None:
        """Text under brief limit (2000 chars) returns unchanged."""
        text = "hello world" * 10  # 110 chars
        assert truncate_output(text, "brief") == text

    def test_long_text_brief_truncated(self) -> None:
        """Text over brief limit is truncated with notice appended."""
        text = "x" * 100 + "\n" * 50 + "y" * 5000  # well over 2000
        result = truncate_output(text, "brief")
        assert len(result) <= 2000
        assert "truncated" in result

    def test_short_text_normal_unchanged(self) -> None:
        """Text under normal limit (8000 chars) returns unchanged."""
        text = "hello world " * 400  # ~4800 chars
        assert truncate_output(text, "normal") == text

    def test_long_text_normal_truncated(self) -> None:
        """Text over normal limit is truncated with notice appended."""
        text = ("a" * 99 + "\n") * 200  # 20000 chars
        result = truncate_output(text, "normal")
        assert len(result) <= 8000
        assert "truncated" in result

    def test_full_never_truncated(self) -> None:
        """Text with detail='full' is never truncated regardless of size."""
        text = "x" * 50000
        assert truncate_output(text, "full") == text

    def test_truncation_notice_includes_line_count(self) -> None:
        """Truncation notice includes accurate count of remaining lines."""
        # 100 lines of 101 chars each (100 + newline) = 10099 chars total
        lines = [("a" * 100) for _ in range(100)]
        text = "\n".join(lines)
        result = truncate_output(text, "brief")  # limit 2000
        # Extract the line count from the notice
        match = re.search(r"(\d+) more line", result)
        assert match is not None, f"No line count found in: {result[-200:]}"
        remaining_lines = int(match.group(1))
        # cut_point = 2000 - 200 = 1800. Lines joined with \n (no trailing \n).
        # Each line is 100 chars, separator is 1 char. Line boundaries at 0, 101, 202...
        # 17 full lines fit in 1717 chars (17*100 + 17 separators = 1717).
        # Remainder from 1800 has 82 \n chars, so remaining.count('\n') + 1 = 83.
        assert remaining_lines == 83

    def test_truncated_text_fits_within_limit(self) -> None:
        """The truncated text plus notice does not exceed the character limit."""
        text = ("z" * 99 + "\n") * 200  # 20000 chars
        for detail in ("brief", "normal"):
            result = truncate_output(text, detail)
            limits = {"brief": 2000, "normal": 8000}
            assert len(result) <= limits[detail], (
                f"detail={detail}: len={len(result)} > {limits[detail]}"
            )

    def test_unknown_detail_treated_as_normal(self) -> None:
        """Unknown detail value (e.g., 'verbose') is treated as 'normal'."""
        short_text = "x" * 5000  # under normal limit (8000)
        assert truncate_output(short_text, "verbose") == short_text

        long_text = "x" * 15000  # over normal limit
        result = truncate_output(long_text, "verbose")
        assert len(result) <= 8000

    def test_empty_string_unchanged(self) -> None:
        """Empty string input returns empty string unchanged."""
        assert truncate_output("", "brief") == ""
        assert truncate_output("", "normal") == ""
        assert truncate_output("", "full") == ""

    def test_text_exactly_at_boundary_unchanged(self) -> None:
        """Text exactly at the limit boundary returns unchanged."""
        text = "x" * 2000
        assert truncate_output(text, "brief") == text

    def test_no_newlines_in_remainder(self) -> None:
        """Text with no newlines reports 1 remaining line."""
        text = "x" * 5000  # no newlines at all
        result = truncate_output(text, "brief")
        match = re.search(r"(\d+) more line", result)
        assert match is not None
        assert int(match.group(1)) == 1


class TestApplyOutputLimitValidation:
    """Tests for apply_output_limit() input validation of detail parameter."""

    def _make_big_result(self, count: int = 200) -> dict:
        """Build a result dict that exceeds 8000 chars."""
        return {
            "items": [{"path": f"res://models/subdirectory/model_{i:04d}.glb", "size": i * 100} for i in range(count)]
        }

    def test_unknown_detail_normalized_to_normal(self) -> None:
        """Unknown detail value applies normal limits (not unlimited)."""
        result = self._make_big_result(200)
        original_size = len(json.dumps(result, default=str))
        assert original_size > 8000  # confirm it's big enough

        output = apply_output_limit(dict(result), "unknown_value", list_keys=["items"])
        assert output.get("output_truncated") is True

    def test_verbose_applies_normal_limits(self) -> None:
        """detail='verbose' applies normal limits, not unlimited."""
        result = self._make_big_result(200)
        output = apply_output_limit(dict(result), "verbose", list_keys=["items"])
        output_size = len(json.dumps(output, default=str))
        assert output_size <= 8000

    def test_empty_string_applies_normal_limits(self) -> None:
        """detail='' (empty string) applies normal limits, not unlimited."""
        result = self._make_big_result(200)
        output = apply_output_limit(dict(result), "", list_keys=["items"])
        assert output.get("output_truncated") is True

    def test_valid_brief_unchanged(self) -> None:
        """detail='brief' continues to work correctly."""
        result = {"items": [{"x": "y"}]}
        output = apply_output_limit(result, "brief")
        assert output == result  # small result unchanged

    def test_valid_normal_unchanged(self) -> None:
        """detail='normal' continues to work correctly."""
        result = {"items": [{"x": "y"}]}
        output = apply_output_limit(result, "normal")
        assert output == result

    def test_valid_full_unchanged(self) -> None:
        """detail='full' continues to work correctly (no limit)."""
        result = self._make_big_result(200)
        items_count = len(result["items"])
        output = apply_output_limit(dict(result), "full", list_keys=["items"])
        # Full should never truncate
        assert len(output["items"]) == items_count
        assert output.get("output_truncated") is not True
