import base64
import os

import httpx
import pytest
from unittest.mock import patch, AsyncMock, MagicMock


@pytest.mark.asyncio
async def test_resolve_http_url():
    """HTTP URL should be returned as-is with type 'url'."""
    from main import resolve_image_input

    result = await resolve_image_input("https://example.com/photo.jpg")
    assert result["type"] == "url"
    assert result["url"] == "https://example.com/photo.jpg"


@pytest.mark.asyncio
async def test_resolve_data_uri():
    """Data URI should be returned as-is with type 'data_uri'."""
    from main import resolve_image_input

    data_uri = "data:image/png;base64,iVBORw0KGgo="
    result = await resolve_image_input(data_uri)
    assert result["type"] == "data_uri"
    assert result["data_uri"] == data_uri


@pytest.mark.asyncio
async def test_resolve_data_uri_force_base64():
    """Data URI with force_base64 should extract raw base64 data."""
    from main import resolve_image_input

    data_uri = "data:image/png;base64,iVBORw0KGgo="
    result = await resolve_image_input(data_uri, force_base64=True)
    assert result["type"] == "base64"
    assert result["data"] == "iVBORw0KGgo="


@pytest.mark.asyncio
async def test_resolve_invalid_data_uri():
    """Invalid data URI (no comma) should raise ValueError."""
    from main import resolve_image_input

    with pytest.raises(ValueError, match="data URI"):
        await resolve_image_input("data:invalid-no-comma")


@pytest.mark.asyncio
async def test_resolve_local_file(tmp_path):
    """Local file should be read and returned as base64 data URI."""
    from main import resolve_image_input

    img_file = tmp_path / "test.png"
    img_file.write_bytes(b"\x89PNG\r\n\x1a\n" + b"\x00" * 100)

    result = await resolve_image_input(str(img_file))
    assert result["type"] == "data_uri"
    assert result["data_uri"].startswith("data:image/png;base64,")


@pytest.mark.asyncio
async def test_resolve_local_file_not_found():
    """Non-existent local file should raise ValueError."""
    from main import resolve_image_input

    with pytest.raises(ValueError, match="文件不存在"):
        await resolve_image_input("/nonexistent/path/image.png")


@pytest.mark.asyncio
async def test_resolve_local_file_too_large(tmp_path):
    """File exceeding max_size should raise ValueError."""
    from main import resolve_image_input

    big_file = tmp_path / "big.png"
    big_file.write_bytes(b"\x00" * (1024 * 1024 + 1))  # 1MB + 1 byte

    with pytest.raises(ValueError, match="文件过大"):
        await resolve_image_input(str(big_file), max_size_mb=1)


@pytest.mark.asyncio
async def test_resolve_http_url_for_ocr():
    """HTTP URL for OCR should download and return base64."""
    from main import resolve_image_input

    mock_response = MagicMock()
    mock_response.content = b"\x89PNG\r\n\x1a\n" + b"\x00" * 50
    mock_response.raise_for_status = MagicMock()

    mock_client = AsyncMock()
    mock_client.get = AsyncMock(return_value=mock_response)
    mock_client.__aenter__ = AsyncMock(return_value=mock_client)
    mock_client.__aexit__ = AsyncMock(return_value=False)

    with patch("httpx.AsyncClient", return_value=mock_client):
        result = await resolve_image_input(
            "https://example.com/doc.png", force_base64=True
        )
    assert result["type"] == "base64"
    assert isinstance(result["data"], str)


@pytest.mark.asyncio
async def test_resolve_http_download_failure():
    """HTTP download failure should raise ValueError with status info."""
    from main import resolve_image_input

    mock_client = AsyncMock()
    mock_client.get = AsyncMock(
        side_effect=httpx.HTTPStatusError(
            "Not Found",
            request=MagicMock(),
            response=MagicMock(status_code=404),
        )
    )
    mock_client.__aenter__ = AsyncMock(return_value=mock_client)
    mock_client.__aexit__ = AsyncMock(return_value=False)

    with patch("httpx.AsyncClient", return_value=mock_client):
        with pytest.raises(ValueError, match="下载失败"):
            await resolve_image_input(
                "https://example.com/missing.png", force_base64=True
            )


@pytest.mark.asyncio
async def test_analyze_image_with_url():
    """analyze_image should call OpenAI vision API and return text."""
    from main import analyze_image

    mock_completion = MagicMock()
    mock_completion.choices = [MagicMock()]
    mock_completion.choices[0].message.content = "This is a cat."

    with patch("main.get_openai_client") as mock_client_fn:
        mock_client = AsyncMock()
        mock_client.chat.completions.create = AsyncMock(
            return_value=mock_completion
        )
        mock_client_fn.return_value = mock_client

        result = await analyze_image(
            prompt="What is in this image?",
            image_url="https://example.com/cat.jpg",
        )

    assert result == "This is a cat."
    # Verify URL was passed directly (not downloaded)
    call_args = mock_client.chat.completions.create.call_args
    messages = call_args.kwargs["messages"]
    image_part = messages[0]["content"][1]
    assert image_part["image_url"]["url"] == "https://example.com/cat.jpg"


@pytest.mark.asyncio
async def test_analyze_image_with_local_file(tmp_path):
    """analyze_image should convert local file to base64 data URI."""
    from main import analyze_image

    img_file = tmp_path / "test.png"
    img_file.write_bytes(b"\x89PNG\r\n\x1a\n" + b"\x00" * 100)

    mock_completion = MagicMock()
    mock_completion.choices = [MagicMock()]
    mock_completion.choices[0].message.content = "A test image."

    with patch("main.get_openai_client") as mock_client_fn:
        mock_client = AsyncMock()
        mock_client.chat.completions.create = AsyncMock(
            return_value=mock_completion
        )
        mock_client_fn.return_value = mock_client

        result = await analyze_image(
            prompt="Describe", image_url=str(img_file)
        )

    assert result == "A test image."
    call_args = mock_client.chat.completions.create.call_args
    messages = call_args.kwargs["messages"]
    image_url = messages[0]["content"][1]["image_url"]["url"]
    assert image_url.startswith("data:image/png;base64,")


@pytest.mark.asyncio
async def test_analyze_image_unsupported_format(tmp_path):
    """analyze_image should reject unsupported image formats."""
    from main import analyze_image

    bmp_file = tmp_path / "test.bmp"
    bmp_file.write_bytes(b"BM" + b"\x00" * 100)

    with pytest.raises(ValueError, match="不支持的图片格式"):
        await analyze_image(prompt="Describe", image_url=str(bmp_file))


@pytest.mark.asyncio
async def test_ocr_with_local_file(tmp_path):
    """ocr should call PaddleOCR API and return markdown."""
    from main import ocr

    img_file = tmp_path / "doc.png"
    img_file.write_bytes(b"\x89PNG\r\n\x1a\n" + b"\x00" * 100)

    mock_response = MagicMock()
    mock_response.json = MagicMock(
        return_value={"result": "# Title\n\nSome text content"}
    )
    mock_response.raise_for_status = MagicMock()

    mock_client = AsyncMock()
    mock_client.post = AsyncMock(return_value=mock_response)
    mock_client.__aenter__ = AsyncMock(return_value=mock_client)
    mock_client.__aexit__ = AsyncMock(return_value=False)

    with patch("httpx.AsyncClient", return_value=mock_client):
        with patch.dict(
            os.environ, {"OCR_API_URL": "http://localhost:8080/layout-parsing"}
        ):
            result = await ocr(file_path=str(img_file))

    assert "Title" in result


@pytest.mark.asyncio
async def test_ocr_with_http_url():
    """ocr should download file then call PaddleOCR API."""
    from main import ocr

    mock_download_resp = MagicMock()
    mock_download_resp.content = b"\x89PNG\r\n\x1a\n" + b"\x00" * 50
    mock_download_resp.raise_for_status = MagicMock()

    mock_ocr_resp = MagicMock()
    mock_ocr_resp.json = MagicMock(
        return_value={"result": "# OCR Result\n\nExtracted text"}
    )
    mock_ocr_resp.raise_for_status = MagicMock()

    mock_client = AsyncMock()
    mock_client.get = AsyncMock(return_value=mock_download_resp)
    mock_client.post = AsyncMock(return_value=mock_ocr_resp)
    mock_client.__aenter__ = AsyncMock(return_value=mock_client)
    mock_client.__aexit__ = AsyncMock(return_value=False)

    with patch("httpx.AsyncClient", return_value=mock_client):
        with patch.dict(
            os.environ,
            {"OCR_API_URL": "http://localhost:8080/layout-parsing"},
        ):
            result = await ocr(file_path="https://example.com/doc.png")

    assert "OCR Result" in result


@pytest.mark.asyncio
async def test_ocr_unsupported_format(tmp_path):
    """ocr should reject unsupported file formats."""
    from main import ocr

    txt_file = tmp_path / "doc.txt"
    txt_file.write_text("hello")

    with pytest.raises(ValueError, match="不支持的文件格式"):
        await ocr(file_path=str(txt_file))


@pytest.mark.asyncio
async def test_ocr_api_error(tmp_path):
    """ocr should return user-friendly error when OCR API fails."""
    from main import ocr

    img_file = tmp_path / "doc.png"
    img_file.write_bytes(b"\x89PNG\r\n\x1a\n" + b"\x00" * 100)

    mock_client = AsyncMock()
    mock_client.post = AsyncMock(
        side_effect=httpx.ConnectError("Connection refused")
    )
    mock_client.__aenter__ = AsyncMock(return_value=mock_client)
    mock_client.__aexit__ = AsyncMock(return_value=False)

    with patch("httpx.AsyncClient", return_value=mock_client):
        with patch.dict(
            os.environ, {"OCR_API_URL": "http://localhost:8080/layout-parsing"}
        ):
            with pytest.raises(ValueError, match="OCR API"):
                await ocr(file_path=str(img_file))
