"""ImageParser 单元测试"""

import base64
from unittest.mock import MagicMock

import pytest

from exceptions import ApiError, ParseError
from parsers.image_parser import ImageParser, DEFAULT_VISION_PROMPT


@pytest.fixture
def mock_api_client():
    return MagicMock()


@pytest.fixture
def parser(mock_api_client):
    return ImageParser(mock_api_client)


class TestSupportedExtensions:
    def test_supports_png(self, parser):
        assert ".png" in parser.supported_extensions()

    def test_supports_jpg(self, parser):
        assert ".jpg" in parser.supported_extensions()

    def test_supports_jpeg(self, parser):
        assert ".jpeg" in parser.supported_extensions()

    def test_supports_bmp(self, parser):
        assert ".bmp" in parser.supported_extensions()

    def test_supports_gif(self, parser):
        assert ".gif" in parser.supported_extensions()

    def test_supports_webp(self, parser):
        assert ".webp" in parser.supported_extensions()

    def test_has_six_extensions(self, parser):
        assert len(parser.supported_extensions()) == 6


class TestParse:
    def test_successful_parse(self, mock_api_client, tmp_path):
        """成功解析图片文件，返回 Vision API 的文本描述"""
        img = tmp_path / "photo.png"
        img.write_bytes(b"\x89PNG\r\n\x1a\n" + b"\x00" * 20)

        mock_api_client.vision.return_value = "图片中包含一段中文文字"
        parser = ImageParser(mock_api_client)

        result = parser.parse(str(img))

        assert result == "图片中包含一段中文文字"
        mock_api_client.vision.assert_called_once()

    def test_base64_encoding_correctness(self, mock_api_client, tmp_path):
        """验证传递给 API 的 base64 编码与文件内容一致"""
        raw_bytes = b"\x89PNG\r\n\x1a\nSOME_IMAGE_DATA"
        img = tmp_path / "check.png"
        img.write_bytes(raw_bytes)

        mock_api_client.vision.return_value = "ok"
        parser = ImageParser(mock_api_client)
        parser.parse(str(img))

        call_args = mock_api_client.vision.call_args
        sent_base64 = call_args.kwargs.get("image_base64") or call_args[1].get("image_base64") or call_args[0][1]
        assert base64.b64decode(sent_base64) == raw_bytes

    def test_system_prompt_passed_to_api(self, mock_api_client, tmp_path):
        """验证使用了正确的系统提示词，且包含文件名上下文"""
        img = tmp_path / "prompt.png"
        img.write_bytes(b"\x00")

        mock_api_client.vision.return_value = "text"
        parser = ImageParser(mock_api_client)
        parser.parse(str(img))

        call_args = mock_api_client.vision.call_args
        sent_prompt = call_args.kwargs.get("system_prompt") or call_args[0][0]
        assert DEFAULT_VISION_PROMPT in sent_prompt
        assert "prompt" in sent_prompt

    def test_file_not_found_raises_parse_error(self, parser):
        """文件不存在时抛出 ParseError"""
        with pytest.raises(ParseError) as exc_info:
            parser.parse("/nonexistent/path/missing.png")
        assert exc_info.value.file_name == "missing.png"
        assert "文件读取失败" in exc_info.value.reason

    def test_unreadable_file_raises_parse_error(self, mock_api_client, tmp_path):
        """文件无法读取时抛出 ParseError（使用目录路径模拟不可读文件）"""
        dir_path = tmp_path / "fakefile.jpg"
        dir_path.mkdir()

        parser = ImageParser(mock_api_client)
        with pytest.raises(ParseError) as exc_info:
            parser.parse(str(dir_path))
        assert exc_info.value.file_name == "fakefile.jpg"
        assert "文件读取失败" in exc_info.value.reason

    def test_api_error_raises_parse_error(self, mock_api_client, tmp_path):
        """API 调用失败时抛出 ParseError"""
        img = tmp_path / "api_fail.png"
        img.write_bytes(b"\x89PNG")

        mock_api_client.vision.side_effect = ApiError("服务不可用", status_code=503)
        parser = ImageParser(mock_api_client)

        with pytest.raises(ParseError) as exc_info:
            parser.parse(str(img))
        assert exc_info.value.file_name == "api_fail.png"
        assert "Vision API 调用失败" in exc_info.value.reason

    def test_api_rate_limit_error_raises_parse_error(self, mock_api_client, tmp_path):
        """API 速率限制错误（经重试耗尽后）也被包装为 ParseError"""
        img = tmp_path / "rate.png"
        img.write_bytes(b"\x89PNG")

        mock_api_client.vision.side_effect = ApiError("速率限制重试耗尽", status_code=429)
        parser = ImageParser(mock_api_client)

        with pytest.raises(ParseError) as exc_info:
            parser.parse(str(img))
        assert "Vision API 调用失败" in exc_info.value.reason

    def test_parse_error_contains_filename_for_missing_file(self, parser):
        """ParseError 包含正确的文件名"""
        with pytest.raises(ParseError) as exc_info:
            parser.parse("/tmp/does_not_exist/myimage.jpeg")
        assert exc_info.value.file_name == "myimage.jpeg"
        assert exc_info.value.reason != ""
