import json
import re
import pytest

# ── code under test ──────────────────────────────────────────────────────────

_ANSI_ESCAPE_RE = re.compile(r'\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')

_CONTROL_CHAR_TABLE = {
    c: None
    for c in (*range(0x00, 0x09), 0x0b, 0x0c, *range(0x0e, 0x20), *range(0x7f, 0xa0))
}


def _sanitise_content(content: str) -> str:
    content = content.encode("utf-8", "backslashreplace").decode("utf-8")
    content = _ANSI_ESCAPE_RE.sub("", content)
    content = content.translate(_CONTROL_CHAR_TABLE)
    return content


# ── helpers ───────────────────────────────────────────────────────────────────

def is_json_serialisable(s: str) -> bool:
    try:
        json.dumps(s)
        return True
    except (ValueError, UnicodeEncodeError):
        return False


# ── surrogate handling ────────────────────────────────────────────────────────

class TestSurrogates:
    def test_lone_surrogate_becomes_backslash_escape(self):
        # \udcff is a surrogateescape stand-in for the byte 0xFF
        s = "hello\udcffworld"
        result = _sanitise_content(s)
        assert "\udcff" not in result
        assert is_json_serialisable(result)

    def test_surrogate_pair_encoded_safely(self):
        s = "\ud83d\ude00"  # not a proper pair in a str — both stay as surrogates
        result = _sanitise_content(s)
        assert is_json_serialisable(result)

    def test_no_surrogates_unchanged(self):
        s = "clean string"
        assert _sanitise_content(s) == s


# ── ANSI escape sequences ─────────────────────────────────────────────────────

class TestAnsiEscapes:
    def test_sgr_color_code_stripped(self):
        s = "\x1b[31mred text\x1b[0m"
        assert _sanitise_content(s) == "red text"

    def test_cursor_movement_stripped(self):
        s = "\x1b[2Jhello"   # ED: erase display
        assert _sanitise_content(s) == "hello"

    def test_single_char_escape_stripped(self):
        s = "\x1bM"          # RI: reverse index (Fe sequence)
        assert _sanitise_content(s) == ""

    def test_bold_and_reset(self):
        s = "\x1b[1mbold\x1b[0m normal"
        assert _sanitise_content(s) == "bold normal"

    def test_256_color_sequence(self):
        s = "\x1b[38;5;200mmagenta\x1b[0m"
        assert _sanitise_content(s) == "magenta"

    def test_no_ansi_unchanged(self):
        s = "plain text"
        assert _sanitise_content(s) == s


# ── control character stripping ───────────────────────────────────────────────

class TestControlChars:
    @pytest.mark.parametrize("char", [
        "\x00",  # NUL
        "\x01",  # SOH
        "\x07",  # BEL
        "\x08",  # BS
        "\x0b",  # VT
        "\x0c",  # FF
        "\x0e",  # SO
        "\x1f",  # US
        "\x7f",  # DEL
        "\x80",  # C1 start
        "\x9f",  # C1 end
    ])
    def test_control_char_stripped(self, char):
        result = _sanitise_content(f"before{char}after")
        assert char not in result
        assert result == "beforeafter"

    def test_tab_preserved(self):
        s = "col1\tcol2"
        assert _sanitise_content(s) == s

    def test_newline_preserved(self):
        s = "line1\nline2"
        assert _sanitise_content(s) == s

    def test_carriage_return_preserved(self):
        s = "line1\r\nline2"
        assert _sanitise_content(s) == s


# ── JSON serialisability (the core guarantee) ─────────────────────────────────

class TestJsonSerialisable:
    @pytest.mark.parametrize("raw", [
        "\x1b[31mred\x1b[0m",
        "hello\udcffworld",
        "\x00\x01\x02",
        "\x1b[38;5;200m\udcfe some output \x07",
        "perfectly normal string",
        "unicode: café, 日本語, emoji: 😀",
        "\t\n\r preserved",
    ])
    def test_output_is_always_json_serialisable(self, raw):
        assert is_json_serialisable(_sanitise_content(raw))


# ── combined / realistic inputs ───────────────────────────────────────────────

class TestRealistic:
    def test_mixed_ansi_and_control_chars(self):
        s = "\x1b[32mOK\x1b[0m\x07 done\x00"
        result = _sanitise_content(s)
        assert result == "OK done"
        assert is_json_serialisable(result)

    def test_binary_garbage_from_surrogateescape(self):
        # Simulate reading a binary file with surrogateescape
        raw_bytes = bytes(range(0x80, 0xA0))
        s = raw_bytes.decode("utf-8", "surrogateescape")
        result = _sanitise_content(s)
        assert is_json_serialisable(result)

    def test_empty_string(self):
        assert _sanitise_content("") == ""

    def test_only_ansi_becomes_empty(self):
        assert _sanitise_content("\x1b[0m") == ""
