# -*- coding: utf-8 -*-
"""EncodingDetector 文件编码检测器测试（Phase 6 Tier 1-3 AO）。

借鉴 zhikuncode-main EncodingDetector.java：
- BOM 检测（UTF-8/UTF-16 LE/BE）
- 二进制检测（null 字节 + 控制字符比例）
- UTF-8 有效性验证
- ISO-8859-1 fallback（全字节范围有效）

策略：BOM → 二进制检测 → UTF-8 验证 → ISO-8859-1 fallback。
返回 None 表示二进制文件。
"""
import os
from pathlib import Path

import pytest

from abyss.tools.file_io import EncodingDetector


@pytest.fixture
def make_file(tmp_path: Path):
    """工厂 fixture：创建文件并返回路径。"""
    def _make(name: str, data: bytes) -> str:
        path = tmp_path / name
        path.write_bytes(data)
        return str(path)
    return _make


class TestEncodingDetectorBOM:
    """BOM 检测测试。"""

    def test_detects_utf8_bom(self, make_file):
        path = make_file("utf8_bom.txt", b"\xef\xbb\xbfhello")
        assert EncodingDetector.detect(path) == "utf-8-sig"

    def test_detects_utf16_le_bom(self, make_file):
        path = make_file("utf16le.txt", b"\xff\xfe" + "hello".encode("utf-16-le"))
        assert EncodingDetector.detect(path) == "utf-16-le"

    def test_detects_utf16_be_bom(self, make_file):
        path = make_file("utf16be.txt", b"\xfe\xff" + "hello".encode("utf-16-be"))
        assert EncodingDetector.detect(path) == "utf-16-be"

    def test_utf8_bom_with_chinese(self, make_file):
        """UTF-8 BOM + 中文内容。"""
        path = make_file("utf8_bom_cn.txt", b"\xef\xbb\xbf" + "中文".encode("utf-8"))
        assert EncodingDetector.detect(path) == "utf-8-sig"


class TestEncodingDetectorBinary:
    """二进制文件检测测试。"""

    def test_detects_binary_returns_none(self, make_file):
        """含大量 null 字节的二进制文件应返回 None。"""
        data = bytes(range(256)) * 32  # 含大量 null 和控制字符
        path = make_file("binary.bin", data)
        assert EncodingDetector.detect(path) is None

    def test_detects_null_heavy_binary(self, make_file):
        """超过 1% null 字节视为二进制。"""
        data = b"\x00" * 100 + b"x" * 100
        path = make_file("null_heavy.bin", data)
        assert EncodingDetector.detect(path) is None

    def test_pure_text_not_treated_as_binary(self, make_file):
        """纯文本不应被识别为二进制。"""
        path = make_file("pure.txt", b"hello world\nthis is text\n")
        result = EncodingDetector.detect(path)
        assert result is not None
        assert result != ""

    def test_all_zero_bytes_short_file(self, make_file):
        """短文件全是 null 字节也视为二进制。"""
        path = make_file("zeros.bin", b"\x00" * 10)
        assert EncodingDetector.detect(path) is None


class TestEncodingDetectorUTF8NoBOM:
    """无 BOM UTF-8 检测测试。"""

    def test_detects_utf8_without_bom(self, make_file):
        path = make_file("plain.txt", b"hello world")
        assert EncodingDetector.detect(path) == "utf-8"

    def test_detects_utf8_chinese_without_bom(self, make_file):
        """中文 UTF-8 编码无 BOM。"""
        path = make_file("cn.txt", "中文测试".encode("utf-8"))
        assert EncodingDetector.detect(path) == "utf-8"

    def test_detects_utf8_emoji(self, make_file):
        """含 emoji 的 UTF-8 文件。"""
        path = make_file("emoji.txt", "🚀🎉test".encode("utf-8"))
        assert EncodingDetector.detect(path) == "utf-8"

    def test_detects_utf8_mixed_content(self, make_file):
        """混合 ASCII + 中文 + emoji。"""
        text = "Hello 世界 🌍\nMixed 内容\n"
        path = make_file("mixed.txt", text.encode("utf-8"))
        assert EncodingDetector.detect(path) == "utf-8"


class TestEncodingDetectorFallback:
    """fallback 策略测试。"""

    def test_falls_back_to_utf8_for_empty_file(self, make_file):
        path = make_file("empty.txt", b"")
        assert EncodingDetector.detect(path) == "utf-8"

    def test_invalid_utf8_falls_back_to_iso_8859_1(self, make_file):
        """非 UTF-8 编码的纯字节序列回退到 ISO-8859-1。

        使用 0xFF 字节（UTF-8 中非法起始字节）确保 UTF-8 验证失败，
        但 0xFF 在 ISO-8859-1 中是合法字符（ÿ）。
        """
        # 0xFF 在 UTF-8 中非法，但 ISO-8859-1 合法
        data = b"caf\xe9\xff"  # 末尾 0xFF 强制 UTF-8 验证失败
        path = make_file("iso.txt", data)
        result = EncodingDetector.detect(path)
        assert result == "iso-8859-1"

    def test_high_bytes_no_null_returns_iso_or_utf8(self, make_file):
        """高字节无 null 字节，应返回 iso-8859-1 或 utf-8（视内容而定）。"""
        data = bytes([0xC0, 0xC1, 0xE0])  # 非 UTF-8 起始字节
        path = make_file("high.txt", data)
        result = EncodingDetector.detect(path)
        assert result in ("iso-8859-1", "utf-8")


class TestEncodingDetectorConstants:
    """常量验证测试。"""

    def test_bom_map_contains_three_boms(self):
        assert len(EncodingDetector.BOM_MAP) == 3

    def test_bom_map_utf8_sig(self):
        boms = dict(EncodingDetector.BOM_MAP)
        assert boms[b"\xef\xbb\xbf"] == "utf-8-sig"

    def test_bom_map_utf16_le(self):
        boms = dict(EncodingDetector.BOM_MAP)
        assert boms[b"\xff\xfe"] == "utf-16-le"

    def test_bom_map_utf16_be(self):
        boms = dict(EncodingDetector.BOM_MAP)
        assert boms[b"\xfe\xff"] == "utf-16-be"

    def test_detect_buffer_size_is_8192(self):
        assert EncodingDetector.DETECT_BUFFER_SIZE == 8192


class TestEncodingDetectorEdgeCases:
    """边界情况测试。"""

    def test_handles_nonexistent_file(self):
        """不存在的文件应抛出异常或返回 None。"""
        try:
            result = EncodingDetector.detect("/nonexistent/path/file.txt")
            assert result is None
        except (OSError, FileNotFoundError):
            pass  # 抛出异常也可接受

    def test_short_file(self, make_file):
        """1 字节文件。"""
        path = make_file("1byte.txt", b"A")
        assert EncodingDetector.detect(path) == "utf-8"

    def test_partial_utf8_at_buffer_end(self, make_file):
        """检测缓冲区末尾是 UTF-8 多字节序列中间字节，
        应安全截断后判定而非误判。
        """
        # 构造 8192 字节 ASCII + 末尾 3 字节 UTF-8 中文（"中" = E4 B8 AD）
        data = b"x" * 8192 + "中".encode("utf-8")
        path = make_file("trailing_utf8.txt", data)
        # 应识别为 utf-8（不因末尾截断而误判）
        result = EncodingDetector.detect(path)
        # 在检测策略中，前 8192 字节是 ASCII，应判为 utf-8
        assert result == "utf-8"

    def test_control_chars_in_text(self, make_file):
        """包含 \t \n \r 等常见控制字符的文本不应被误判为二进制。"""
        text = "line1\n\tindented\rcarriage\nline2\n"
        path = make_file("controls.txt", text.encode("utf-8"))
        result = EncodingDetector.detect(path)
        assert result == "utf-8"

    def test_gbk_content_no_bom(self, make_file):
        """无 BOM 的 GBK 中文文件。

        GBK 是双字节编码，每个字节都在 0x81-0xFE 范围，
        多数 GBK 序列不是合法 UTF-8，应回退到 iso-8859-1。
        """
        # "中文" GBK 编码 = D6 D0 CE C4
        data = "中文".encode("gbk")
        path = make_file("gbk.txt", data)
        # GBK 字节序列不是合法 UTF-8，应回退到 iso-8859-1
        result = EncodingDetector.detect(path)
        # GBK 字节是 D6 D0 CE C4，无 null 字节，非二进制
        # 不是合法 UTF-8 → 应回退到 iso-8859-1
        assert result == "iso-8859-1"


class TestEncodingDetectorPureAscii:
    """纯 ASCII 内容测试。"""

    def test_pure_ascii_returns_utf8(self, make_file):
        """纯 ASCII 是 UTF-8 子集，应返回 utf-8。"""
        path = make_file("ascii.txt", b"Hello, World!\n")
        assert EncodingDetector.detect(path) == "utf-8"

    def test_ascii_with_newlines(self, make_file):
        path = make_file("multi.txt", b"line1\nline2\nline3\n")
        assert EncodingDetector.detect(path) == "utf-8"

    def test_ascii_long_content(self, make_file):
        path = make_file("long.txt", b"a" * 10000)
        assert EncodingDetector.detect(path) == "utf-8"
