# tests/test_github_issues.py
"""Regression tests from chardet/chardet GitHub issues.

Each test corresponds to a specific bug report with a reproducible test case.
Tests are grouped by category: short UTF-8, emoji, wrong encoding, escape
sequences, CJK, UTF-16/32, and miscellaneous.

Issues marked xfail are known limitations (ambiguous short inputs, pipeline
ordering issues) documented for future improvement.
"""

from __future__ import annotations

import chardet
from chardet.enums import EncodingEra
from chardet.equivalences import is_correct, is_equivalent_detection


def _assert_detection(
    data: bytes,
    expected: str,
    *,
    era: EncodingEra = EncodingEra.ALL,
) -> None:
    """Assert that chardet detects the expected encoding (or an equivalent)."""
    result = chardet.detect(data, encoding_era=era, prefer_superset=True)
    detected = result["encoding"]
    assert is_correct(expected, detected) or is_equivalent_detection(
        data, expected, detected
    ), f"expected={expected}, got={detected} (confidence={result['confidence']:.2f})"


# =========================================================================
# SHORT INPUT / SINGLE CHARACTER UTF-8 DETECTION
# These were the most commonly reported class of bug in original chardet.
# =========================================================================


class TestShortUtf8:
    """Short UTF-8 strings with one or two multi-byte characters."""

    def test_single_eacute(self) -> None:
        """Issue #37: Single e-acute (2 bytes) detected as windows-1252."""
        _assert_detection(b"\xc3\xa9", "utf-8")

    def test_double_eacute(self) -> None:
        """Issue #37: Two e-acute chars should also work."""
        _assert_detection(b"\xc3\xa9\xc3\xa9", "utf-8")

    def test_foo_eacute(self) -> None:
        """Issue #134: 'foo e-acute' detected as ISO-8859-1."""
        _assert_detection(b"foo \xc3\xa9", "utf-8")

    def test_degree_symbol(self) -> None:
        """Issue #305: UTF-8 degree symbol detected as ISO-8859-1."""
        _assert_detection(b"\xc2\xb0", "utf-8")

    def test_german_umlaut_in_sentence(self) -> None:
        """Issue #288: German text with o-umlaut detected as ISO-8859-9."""
        _assert_detection(b"Sch\xc3\xb6ne gesunde Pflanzen", "utf-8")

    def test_pokemon_slogan(self) -> None:
        """Issue #308: Mostly-ASCII UTF-8 with e-acute detected as MacRoman."""
        _assert_detection(b'___!" (Pok\xc3\xa9mon slogan)', "utf-8")

    def test_bullet_character(self) -> None:
        """Issue #61: UTF-8 bullet (U+2022) detected as windows-1252."""
        _assert_detection(b"FAHR\xe2\x80\xa2WERK", "utf-8")

    def test_right_single_quotation(self) -> None:
        """Issue #185: Right single quotation mark detected as windows-1252."""
        _assert_detection(b"Carter\xe2\x80\x99s Janitorial", "utf-8")

    def test_python_file_with_umlaut(self) -> None:
        """Issue #75: UTF-8 Python file with single u-umlaut."""
        _assert_detection(
            b"#!/usr/bin/env python3\n# coding: utf-8\n#\n"
            b"###############################################"
            b"#############################\n\n"
            b"__version__ = '1.0'\n__author__ = '\xc3\xbc'\n",
            "utf-8",
        )

    def test_csv_with_umlaut(self) -> None:
        """Issue #138: UTF-8 CSV with single a-umlaut detected as ISO-8859-1."""
        _assert_detection(
            b'"Companyname","Prename","Surename","Streetname","ZIP","City",'
            b'"Phone","Fax","Email","Website","Category"\n'
            b'"Whatever GmbH","Mike","H\xc3\xa4n","Burger Str 8C","39925",'
            b'"Bonn","+49 511 123432","+49 511 1234312",'
            b'"bonn@whatever.com","http://www.whatever.de","Business"\n',
            "utf-8",
        )

    def test_gebuehrenfrei(self) -> None:
        """Issue #28: 'gebuehrenfrei' with u-umlaut."""
        _assert_detection(b"geb\xc3\xbchrenfrei", "utf-8")

    def test_example_aacute(self) -> None:
        """Issue #28: 'example' with a-acute."""
        _assert_detection(b"ex\xc3\xa1mple", "utf-8")

    def test_naive_idiaeresis(self) -> None:
        """Issue #28: 'naive' with i-diaeresis."""
        _assert_detection(b"na\xc3\xafve", "utf-8")

    def test_sie_hoeren(self) -> None:
        """Issue #28: 'sie hoeren' with o-umlaut."""
        _assert_detection(b"sie h\xc3\xb6ren", "utf-8")

    def test_section_sign(self) -> None:
        """Issue #308: Section sign (U+00A7) detected as TIS-620."""
        _assert_detection(b"42 CFR \xc2\xa7 400.200\n", "utf-8")

    def test_cinecitt_agrave(self) -> None:
        """Issue #60: 'Cinecitt-a-grave Make' detected as ISO-8859-2."""
        _assert_detection(b"Cinecitt\xc3\xa0 Make", "utf-8")

    def test_utf8_extended_latin(self) -> None:
        """Issue #292: UTF-8 with extended Latin chars detected as Windows-1254."""
        _assert_detection(
            b"# test data with some utf-8 sequences\n"
            b"\xc4\x80\xc4\x81\xc4\x82\xc4\x83"
            b"\xc3\x90"
            b" some more ascii text here\n",
            "utf-8",
        )

    def test_utf8_abcapitalia(self) -> None:
        """Issue #160: UTF-8 'ab-Capital I with grave-a' misdetected."""
        _assert_detection(b"\x61\x62\xc3\x8f\x61", "utf-8")


# =========================================================================
# UTF-8 WITH EMOJI / 4-BYTE SEQUENCES
# =========================================================================


class TestUtf8Emoji:
    """UTF-8 with 4-byte emoji sequences."""

    def test_purple_heart_emoji(self) -> None:
        """Issue #128: Purple heart emoji breaks UTF-8 detection."""
        _assert_detection(
            b'scriptencoding utf-8\n" \xf0\x9f\x92\x9c\n'
            b'" set list listchars=tab:\xc2\xbb\xc2\xb7,trail:\xc2\xb7,'
            b"eol:\xc2\xac,nbsp:_,extends:\xe2\x9d\xaf,precedes:\xe2\x9d\xae\n",
            "utf-8",
        )

    def test_cat_emoji(self) -> None:
        """Issue #28: Cat emoji in UTF-8."""
        _assert_detection(b"This is a cat \xf0\x9f\x98\xb8", "utf-8")


# =========================================================================
# UTF-8 BOM
# =========================================================================


class TestUtf8Bom:
    """UTF-8 BOM (byte order mark) detection."""

    def test_bom_with_crlf(self) -> None:
        """Issue #34: UTF-8 BOM with Windows line endings."""
        _assert_detection(b"\xef\xbb\xbf\r\n#include <stdio.h>\r\n", "utf-8-sig")

    def test_bom_hello_world(self) -> None:
        """Issue #30: UTF-8 BOM should be detected as utf-8-sig."""
        _assert_detection(b"\xef\xbb\xbfHello World", "utf-8-sig")


# =========================================================================
# ESCAPE SEQUENCE / HZ-GB-2312 FALSE POSITIVES
# =========================================================================


class TestEscapeSequences:
    """Escape sequence and HZ-GB-2312 false positive prevention."""

    def test_tilde_brace_not_hz(self) -> None:
        """Issue #82: ASCII text with ~{ and ~} not falsely detected as HZ-GB-2312."""
        _assert_detection(b"~{,\n~},\n", "ascii")

    def test_tilde_brace_inline(self) -> None:
        """Issue #290: 'xxx~{xxx' should not trigger HZ-GB-2312."""
        _assert_detection(b"xxx~{xxx", "ascii")

    def test_esc_in_utf8(self) -> None:
        """Issue #65: UTF-8 with trailing ESC byte returns None."""
        _assert_detection(b"\xc8\x8d\x1b", "utf-8")

    def test_esc_in_ascii_long(self) -> None:
        """Issue #63: Long ASCII text with single trailing ESC byte."""
        # With enough ASCII bytes, the single ESC should not trigger binary
        _assert_detection(b"0" * 100 + b"\x1b", "ascii")


# =========================================================================
# UTF-16 / UTF-32 ISSUES
# =========================================================================


class TestUtf1632:
    """UTF-16 and UTF-32 detection edge cases."""

    def test_utf16_with_null_after_bom(self) -> None:
        """Issue #62: UTF-16 BOM + U+0000 + U+0030 misread as UTF-32-LE."""
        _assert_detection(b"\xff\xfe\x00\x000\x00", "utf-16")

    def test_utf16_le_bom_returns_utf16(self) -> None:
        """Issue #364: BOM-prefixed UTF-16 should return 'utf-16' (not -le/-be).

        Python's 'utf-16' codec strips the BOM on decode; 'utf-16-le' does not.
        """
        _assert_detection(b"\xff\xfeH\x00e\x00l\x00l\x00o\x00", "utf-16")

    def test_utf16_be_bom_returns_utf16(self) -> None:
        """Issue #364: BOM-prefixed UTF-16-BE should return 'utf-16'."""
        _assert_detection(b"\xfe\xff\x00H\x00e\x00l\x00l\x00o", "utf-16")

    def test_utf32_le_bom_returns_utf32(self) -> None:
        """Issue #364: BOM-prefixed UTF-32 should return 'utf-32'."""
        _assert_detection(b"\xff\xfe\x00\x00H\x00\x00\x00", "utf-32")

    def test_utf32_be_bom_returns_utf32(self) -> None:
        """Issue #364: BOM-prefixed UTF-32-BE should return 'utf-32'."""
        _assert_detection(b"\x00\x00\xfe\xff\x00\x00\x00H", "utf-32")

    def test_utf16le_no_bom(self) -> None:
        """Issue #105: UTF-16LE without BOM detected via null-byte pattern."""
        _assert_detection(
            b"H\x00e\x00l\x00l\x00o\x00 \x00W\x00o\x00r\x00l\x00d\x00",
            "utf-16-le",
        )


# =========================================================================
# CJK ENCODING ISSUES (short inputs)
# =========================================================================


class TestCjkShortInputs:
    """CJK encoding detection with very short inputs.

    These are inherently ambiguous: a handful of high bytes can be valid
    in multiple single-byte AND multi-byte encodings.  Our CJK gating
    requires a minimum number of non-ASCII bytes to avoid false positives.
    """

    def test_single_chinese_char_gb2312(self) -> None:
        """Issue #219: Single Chinese char in GB2312 detected as Cyrillic."""
        _assert_detection(b"\xd6\xd0", "gb2312")

    def test_korean_name_euckr(self) -> None:
        """Issue #161: Korean name (6 bytes) in EUC-KR."""
        _assert_detection(b"\xb1\xe8\xbc\xba\xbd\xc4", "euc-kr")

    def test_chinese_in_ascii_path(self) -> None:
        """Issue #294: Two Chinese chars in a file path."""
        _assert_detection(
            b"*file_import {D:/\xc9\xe8\xbc\xc6-1.step} {Step Files}",
            "gb2312",
        )

    def test_chinese_gb2312_text(self) -> None:
        """Issue #247: Chinese GB2312 text (10 bytes, enough signal)."""
        _assert_detection(b"\xb0\xb2\xbb\xd5\xb9\xe3\xcc\xb6\xb3\xa1", "gb2312")


# =========================================================================
# GB18030 / BOM ISSUES
# =========================================================================


class TestGb18030:
    """GB18030 detection."""

    def test_gb18030_with_bom(self) -> None:
        """Issue #178: GB18030 text with BOM detected as windows-1252."""
        _assert_detection(
            b"\x841\x953"  # GB18030 encoding of U+FEFF (BOM)
            b"\xce\xd2\xc3\xbb\xd3\xd0\xc2\xf1\xd4\xb9\xa3\xac"
            b"\xb4\xe8\xc5\xe8\xb5\xc4\xd6\xbb\xca\xc7\xd2\xbb"
            b"\xd0\xa9\xca\xb1\xbc\xe4\xa1\xa3",
            "gb18030",
        )


# =========================================================================
# WRONG ENCODING WITH MODERN_WEB ERA
# These pass with MODERN_WEB (the default) but may fail with ALL era.
# =========================================================================


class TestWrongEncodingModernWeb:
    """Detection failures that are fixed with the default MODERN_WEB era."""

    def test_portuguese_iso88591(self) -> None:
        """Issue #24: Portuguese ISO-8859-1 text detected as Cyrillic."""
        _assert_detection(
            b'"ULTIMA ATUALIZACAO";"17/03/2014 04:01"\r\n'
            b'"ANO";"MES";"SENADOR";"TIPO_DESPESA";"CNPJ_CPF";'
            b'"FORNECEDOR";"DOCUMENTO";"DATA";"DETALHAMENTO";'
            b'"VALOR_REEMBOLSADO"\r\n'
            b'"2011";"1";"ACIR GURGACZ";"Aluguel de im\xf3veis para '
            b"escrit\xf3rio pol\xedtico, compreendendo despesas "
            b'concernentes a eles.";"05.914.650/0001-66";"CERON - '
            b"CENTRAIS EL\xc9TRICAS DE ROND\xd4NIA "
            b'S.A.";"45216633";"11/01/11";"";"47,65"\r\n',
            "iso-8859-1",
        )

    def test_smart_apostrophe_win1252(self) -> None:
        """Issue #53: MS smart apostrophe (0x92) detected correctly."""
        _assert_detection(
            b"today\x92s research", "windows-1252", era=EncodingEra.MODERN_WEB
        )

    def test_latin1_accented_chars(self) -> None:
        """Issue #242: Latin-1 text with accented chars."""
        _assert_detection(
            b"latin-1 encoded string > \xe9\xe1\xfb",
            "iso-8859-1",
            era=EncodingEra.MODERN_WEB,
        )

    def test_subtitle_acute_apostrophes(self) -> None:
        """Issue #279: Subtitle text with acute accents as apostrophes."""
        _assert_detection(
            b"y!\r\n- We\xb4re going to get him.\r\n- He was here.\r\n"
            b"Don\xb4t worry, we\xb4ll find him.\r\n"
            b"I\xb4m sure he\xb4s around here somewhere.\r\n"
            b"Let\xb4s keep looking.\r\n",
            "iso-8859-1",
            era=EncodingEra.MODERN_WEB,
        )

    def test_iso88591_pound_middot(self) -> None:
        """Issue #170: ISO-8859-1 English with pound sign and middle dots."""
        _assert_detection(
            b"OTE up to \xa350K first year!. to emergency situations "
            b"\xb7 perform all activities with children, i.e. jump, "
            b"dance, walk, run, etc. for extended periods of time "
            b"\xb7 must possess acceptable hearing... . oh. "
            b"to emergency situations \xb7 perform all activities "
            b"with children, i.e. jump, dance, walk, run, etc. for "
            b"extended periods of time \xb7 both indoor and outdoor..."
            b" . ok. for the public including lectures, concerts, "
            b"recitals, dramatic productions, dance performances, "
            b"films, and art exhibits. laurens county renowned "
            b"quality of... . sc.",
            "iso-8859-1",
            era=EncodingEra.MODERN_WEB,
        )


# =========================================================================
# REMAINING KNOWN FAILURES
# =========================================================================


class TestKnownFailures:
    """Cases that still fail — documented for future improvement."""

    def test_french_win1252_anonymized(self) -> None:
        """Issue #96: French windows-1252 text detected as windows-1251."""
        _assert_detection(
            b"xxxx), xx xxxxxx xx xxxxxx xx\xe9\xe9x \xe0 xxxxx x xxxx \n"
            b"xxxxx\xe9 xx xx xxx xxxxxxx\xe9.\n\n*__*\n\n"
            b"xx *xxx, x. xxxxx*, xxxxx xxxxxxx xx xxxxxx xxxx "
            b"x\xe9xxxxxx \xe0 xxxxxxxx \n"
            b"xxxx xxx xxxxxxxxx xxxxxx\xe9xx xxx xxx "
            b"xxxxxxxxxxxxxxx xxxx xx xx xx xx \n"
            b"xxxxxxx xx\xe9x\xe9xxxxx :\n\n"
            b"- xx xxxxxx x\\xxxxxxxxxxxx xxxxxxxxxx \xe0 "
            b"x\\xxxxxxxxxxxxxx\xe9 xx xxxx xx xxxx : xxx \n"
            b"xx ; xxx (xxxx) / xxx (xxxx) xxxxxx.\n\n"
            b"- xxx x\xe9xxxxxx xxx xxxxxxxxxxxx xxxx xxxx : "
            b"xx xxxx\xe9xxxxxxx xxxxxxxxx \n"
            b"(xx xxx xxx \\xxxxx) / xxxxxxxxx (xx xxx xxx \\xxxxx) "
            b"xxx xxxxxxx x\\xxxxxxxx xxxxxxxxx\xe9 \n"
            b"xx xxx.\n\n"
            b"- xxx x\xe9xxxxxx xxx xxxxxx xx xxxxxxxxx xxxx xxxx : "
            b"xx xxxx\xe9xxxxx xxxxx \n"
            b"xxx x\xe9xxxxxx xxxx xx xxxxxxx (xx xxx xxx \\xxxxx) "
            b"xx xxxxxx xxxx xx xxxxxx (xx \n"
            b"xxx xxx \\xxxxx) x\\xxxxxxxxxxxxx xxx xx xx\xfbx xxxx "
            b"xxxxxxxxx xxx xxxxxx (xxxxxxxxx \n"
            b"xxxx xxxxxx, xxxxx xx xxxxxxxxx xxxx xxxxxxxxx ...) "
            b"xx x\\xxxxxxxxxxx xx \n"
            b"xxxxxxxxxx xxx\xe9xxxxxxx xxxx xxxxxxxx xxxxxxx.\n\n"
            b"- xxx xxxxxx x\\xxxxxxxxxxxxxxxx : xx xxxx\xe9xxxxx "
            b"xx xxxxxxxx xx\xe9xxxx xxxxx \n"
            b"xxxx xx xxxx xxxxxxx\xe9x xx xxxxxxx xxxxx\xe8",
            "windows-1252",
        )


# =========================================================================
# WINDOWS-1252 SPECIFIC BYTE DETECTION
# =========================================================================


class TestWindows1252Bytes:
    """Detection of bytes unique to windows-1252 (C1 range 0x80-0x9F)."""

    def test_euro_sign_win1252(self) -> None:
        """Issue #317: Single euro sign in windows-1252."""
        _assert_detection(b"\x80", "windows-1252")


# =========================================================================
# ISO-8859-7 (GREEK) ISSUES
# =========================================================================


class TestGreek:
    """ISO-8859-7 Greek text detection."""

    def test_nbsp_with_angle_bracket(self) -> None:
        """Issue #64: NBSP (0xa0) in short context."""
        _assert_detection(b"<\xa0", "iso-8859-7")

    def test_greek_text_omilia(self) -> None:
        """Issue #124: Greek text 'Me omilia tis' in ISO-8859-7."""
        _assert_detection(
            b"\xcc\xe5 \xef\xec\xe9\xeb\xdf\xe1 \xf4\xe7\xf2",
            "iso-8859-7",
        )


# =========================================================================
# NO-CRASH TESTS (issues that caused exceptions in original chardet)
# =========================================================================


class TestNoCrash:
    """Inputs that caused exceptions in original chardet."""

    def test_issue_67_no_crash(self) -> None:
        r"""Issue #67: b'\xfe\xcf' caused IndexError in original chardet."""
        result = chardet.detect(b"\xfe\xcf", encoding_era=EncodingEra.ALL)
        # Just verify it doesn't crash; any result is acceptable
        assert isinstance(result, dict)
        assert "encoding" in result

    def test_issue_367_short_two_byte_runtime_error(self) -> None:
        r"""Issue #367: b'\xf9\x92' raised RuntimeError.

        Two-byte sequences that are valid multi-byte (e.g. cp932) score
        above the structural confidence threshold, but statistical scoring
        returns no results due to insufficient data.  The pipeline must
        fall through to the fallback instead of returning an empty list.
        """
        # The exact input from the bug report
        result = chardet.detect(b"\xf9\x92")
        assert isinstance(result, dict)
        assert "encoding" in result
        assert result["encoding"] is not None

    def test_issue_367_additional_two_byte_sequences(self) -> None:
        """Issue #367: verify other representative two-byte sequences don't crash.

        Samples chosen from byte ranges that form valid cp932/johab multi-byte
        sequences — the same structural path that triggered the original bug.
        """
        samples = [
            b"\x81\x40",  # cp932 lead byte + valid trail
            b"\xf0\x80",  # cp932 high lead byte
            b"\xe0\xa0",  # cp932 lead byte
            b"\x84\x41",  # johab lead byte
            b"\xd9\xfe",  # johab high lead byte
            b"\xf9\x92",  # original report
        ]
        for data in samples:
            result = chardet.detect(data)
            assert isinstance(result, dict), f"Failed on {data!r}"
            assert "encoding" in result, f"Failed on {data!r}"


# =========================================================================
# NULL SEPARATOR ISSUES
# =========================================================================


class TestNullSeparators:
    """ASCII text with null byte separators."""

    def test_issue_346_null_separated_ascii(self) -> None:
        """Issue #346: Null-separated ASCII detected as utf-16-be."""
        data = (
            b"master:README.md\x002\x00For support slack to #kodiak-support\n"
            b"master:support.txt\x001\x00For support slack to #kodiak-support\n"
        )
        result = chardet.detect(data)
        assert result["encoding"] == "ascii"
        assert result["confidence"] == 0.99

    def test_find_print0_output(self) -> None:
        """Find -print0 style output should be detected as ASCII."""
        data = (
            b"/home/user/documents/report.txt\x00"
            b"/home/user/documents/notes.txt\x00"
            b"/home/user/downloads/image.png\x00"
            b"/home/user/music/song.mp3\x00"
        )
        result = chardet.detect(data)
        assert result["encoding"] == "ascii"
        assert result["confidence"] == 0.99
