"""embed.py 单元测试（mock Ollama HTTP）"""

import httpx
import pytest

from index1.embed import OllamaEmbedder


def _mock_client(handler):
    """构造带 base_url 的 mock AsyncClient"""
    return httpx.AsyncClient(
        transport=httpx.MockTransport(handler),
        base_url="http://fake:11434",
    )


class TestEmbedOne:
    @pytest.mark.asyncio
    async def test_success(self):
        fake_vec = [0.1, 0.2, 0.3, 0.4]

        def handler(request: httpx.Request):
            return httpx.Response(200, json={"embeddings": [fake_vec]})

        embedder = OllamaEmbedder("test-model", "http://fake:11434", dim=4)
        embedder._client = _mock_client(handler)

        result = await embedder.embed_one("hello")
        assert result == fake_vec
        await embedder.close()

    @pytest.mark.asyncio
    async def test_http_error(self):
        def handler(request: httpx.Request):
            return httpx.Response(500, text="Internal Server Error")

        embedder = OllamaEmbedder("test-model", "http://fake:11434")
        embedder._client = _mock_client(handler)

        with pytest.raises(httpx.HTTPStatusError):
            await embedder.embed_one("hello")
        await embedder.close()


class TestEmbedBatch:
    @pytest.mark.asyncio
    async def test_single_batch(self):
        fake_vecs = [[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]

        def handler(request: httpx.Request):
            return httpx.Response(200, json={"embeddings": fake_vecs})

        embedder = OllamaEmbedder("test-model", "http://fake:11434", dim=2)
        embedder._client = _mock_client(handler)

        result = await embedder.embed_batch(["a", "b", "c"])
        assert len(result) == 3
        assert result[0] == [0.1, 0.2]
        await embedder.close()

    @pytest.mark.asyncio
    async def test_multi_batch(self):
        call_count = 0

        def handler(request: httpx.Request):
            nonlocal call_count
            call_count += 1
            import json
            body = json.loads(request.content)
            n = len(body["input"])
            vecs = [[float(call_count)] * 2 for _ in range(n)]
            return httpx.Response(200, json={"embeddings": vecs})

        embedder = OllamaEmbedder("test-model", "http://fake:11434", dim=2)
        embedder._client = _mock_client(handler)

        # 5 个文本，batch_size=2 → 3 次调用
        result = await embedder.embed_batch(["a", "b", "c", "d", "e"], batch_size=2)
        assert len(result) == 5
        assert call_count == 3
        await embedder.close()

    @pytest.mark.asyncio
    async def test_empty_input(self):
        embedder = OllamaEmbedder("test-model", "http://fake:11434")
        result = await embedder.embed_batch([])
        assert result == []
        await embedder.close()


class TestCheckAvailability:
    @pytest.mark.asyncio
    async def test_model_available(self):
        """模型已下载且 embed 正常 → 返回 True"""
        def handler(request: httpx.Request):
            if "/api/tags" in str(request.url):
                return httpx.Response(200, json={
                    "models": [{"name": "nomic-embed-text:latest"}]
                })
            if "/api/embed" in str(request.url):
                return httpx.Response(200, json={
                    "embeddings": [[0.1, 0.2, 0.3]]
                })
            return httpx.Response(404)

        embedder = OllamaEmbedder("nomic-embed-text", "http://fake:11434")
        embedder._client = _mock_client(handler)

        assert await embedder.check_availability() is True
        assert embedder.available is True
        assert embedder.unavailable_reason == ""
        await embedder.close()

    @pytest.mark.asyncio
    async def test_model_not_downloaded(self):
        """模型不在列表里 → 返回 False + 提示 ollama pull"""
        def handler(request: httpx.Request):
            return httpx.Response(200, json={
                "models": [{"name": "llama3:latest"}]
            })

        embedder = OllamaEmbedder("nomic-embed-text", "http://fake:11434")
        embedder._client = _mock_client(handler)

        assert await embedder.check_availability() is False
        assert embedder.available is False
        assert "ollama pull" in embedder.unavailable_reason
        await embedder.close()

    @pytest.mark.asyncio
    async def test_model_downloaded_but_embed_fails(self):
        """模型在列表里但 embed 报错 → 返回 False"""
        def handler(request: httpx.Request):
            if "/api/tags" in str(request.url):
                return httpx.Response(200, json={
                    "models": [{"name": "nomic-embed-text:latest"}]
                })
            if "/api/embed" in str(request.url):
                return httpx.Response(500, text="model loading error")
            return httpx.Response(404)

        embedder = OllamaEmbedder("nomic-embed-text", "http://fake:11434")
        embedder._client = _mock_client(handler)

        assert await embedder.check_availability() is False
        assert embedder.available is False
        assert embedder.unavailable_reason != ""
        await embedder.close()

    @pytest.mark.asyncio
    async def test_model_returns_empty_embeddings(self):
        """模型返回空向量 → 返回 False + 提示模型损坏"""
        def handler(request: httpx.Request):
            if "/api/tags" in str(request.url):
                return httpx.Response(200, json={
                    "models": [{"name": "nomic-embed-text:latest"}]
                })
            if "/api/embed" in str(request.url):
                return httpx.Response(200, json={"embeddings": [[]]})
            return httpx.Response(404)

        embedder = OllamaEmbedder("nomic-embed-text", "http://fake:11434")
        embedder._client = _mock_client(handler)

        assert await embedder.check_availability() is False
        assert "损坏" in embedder.unavailable_reason
        await embedder.close()

    @pytest.mark.asyncio
    async def test_ollama_not_running(self):
        """Ollama 服务未启动 → 返回 False + 提示 ollama serve"""
        def handler(request: httpx.Request):
            raise httpx.ConnectError("Connection refused")

        embedder = OllamaEmbedder("test-model", "http://fake:11434")
        embedder._client = _mock_client(handler)

        assert await embedder.check_availability() is False
        assert embedder.available is False
        assert "ollama serve" in embedder.unavailable_reason
        await embedder.close()

    @pytest.mark.asyncio
    async def test_empty_models(self):
        """空模型列表 → 返回 False + 提示 ollama pull"""
        def handler(request: httpx.Request):
            return httpx.Response(200, json={"models": []})

        embedder = OllamaEmbedder("test-model", "http://fake:11434")
        embedder._client = _mock_client(handler)

        assert await embedder.check_availability() is False
        assert "ollama pull" in embedder.unavailable_reason
        await embedder.close()

    @pytest.mark.asyncio
    async def test_retry_interval_caching(self):
        """失败后冷却期内不重复检测"""
        call_count = 0

        def handler(request: httpx.Request):
            nonlocal call_count
            call_count += 1
            raise httpx.ConnectError("Connection refused")

        embedder = OllamaEmbedder("test-model", "http://fake:11434")
        embedder._client = _mock_client(handler)

        # 第一次调用：实际检测
        assert await embedder.check_availability() is False
        assert call_count == 1

        # 第二次调用：冷却期内，直接返回缓存
        assert await embedder.check_availability() is False
        assert call_count == 1  # 没有新的 HTTP 请求
        await embedder.close()


class TestProperties:
    def test_initial_state(self):
        embedder = OllamaEmbedder("nomic-embed-text", "http://localhost:11434", dim=768)
        assert embedder.model == "nomic-embed-text"
        assert embedder.dim == 768
        assert embedder.available is False  # 未检测 = False
        assert embedder.unavailable_reason == ""  # 初始无原因

    def test_url_trailing_slash(self):
        embedder = OllamaEmbedder("m", "http://localhost:11434/")
        assert embedder._ollama_url == "http://localhost:11434"
