"""doctor.py 单元测试"""

from unittest.mock import patch, MagicMock
import subprocess

import httpx
import pytest

from index1.config import Config
from index1.doctor import (
    _check_model,
    _check_service,
    _find_current_profile,
    _get_ollama_version,
    _test_embedding,
    run_doctor,
)


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


class TestGetOllamaVersion:
    def test_parses_version(self):
        with patch("subprocess.run") as mock_run:
            mock_run.return_value = MagicMock(
                stdout="ollama version is 0.15.4\n", stderr=""
            )
            assert _get_ollama_version("/usr/bin/ollama") == "0.15.4"

    def test_version_in_stderr(self):
        with patch("subprocess.run") as mock_run:
            mock_run.return_value = MagicMock(
                stdout="", stderr="Warning: could not connect\nollama version is 0.15.4"
            )
            assert _get_ollama_version("/usr/bin/ollama") == "0.15.4"

    def test_oserror(self):
        with patch("subprocess.run", side_effect=OSError("not found")):
            assert _get_ollama_version("/bad/path") is None

    def test_timeout(self):
        with patch("subprocess.run", side_effect=subprocess.TimeoutExpired("cmd", 5)):
            assert _get_ollama_version("/usr/bin/ollama") is None


class TestCheckService:
    @pytest.mark.asyncio
    async def test_service_running(self):
        handler = lambda req: httpx.Response(200, json={"version": "0.15.4"})
        client = _mock_client(handler)
        try:
            assert await _check_service("http://fake:11434", _client=client) is True
        finally:
            await client.aclose()

    @pytest.mark.asyncio
    async def test_service_not_running(self):
        def handler(req):
            raise httpx.ConnectError("Connection refused")

        client = _mock_client(handler)
        try:
            assert await _check_service("http://fake:11434", _client=client) is False
        finally:
            await client.aclose()


class TestCheckModel:
    @pytest.mark.asyncio
    async def test_model_found(self):
        handler = lambda req: httpx.Response(
            200, json={"models": [{"name": "nomic-embed-text:latest"}]}
        )
        client = _mock_client(handler)
        try:
            assert await _check_model("http://fake:11434", "nomic-embed-text", _client=client) is True
        finally:
            await client.aclose()

    @pytest.mark.asyncio
    async def test_model_not_found(self):
        handler = lambda req: httpx.Response(
            200, json={"models": [{"name": "llama3:latest"}]}
        )
        client = _mock_client(handler)
        try:
            assert await _check_model("http://fake:11434", "nomic-embed-text", _client=client) is False
        finally:
            await client.aclose()

    @pytest.mark.asyncio
    async def test_empty_models(self):
        handler = lambda req: httpx.Response(200, json={"models": []})
        client = _mock_client(handler)
        try:
            assert await _check_model("http://fake:11434", "nomic-embed-text", _client=client) is False
        finally:
            await client.aclose()


class TestTestEmbedding:
    @pytest.mark.asyncio
    async def test_success(self):
        fake_vec = [0.1] * 768
        handler = lambda req: httpx.Response(200, json={"embeddings": [fake_vec]})
        client = _mock_client(handler)
        try:
            ok, dim = await _test_embedding("http://fake:11434", "nomic-embed-text", _client=client)
            assert ok is True
            assert dim == 768
        finally:
            await client.aclose()

    @pytest.mark.asyncio
    async def test_failure(self):
        handler = lambda req: httpx.Response(500, text="error")
        client = _mock_client(handler)
        try:
            ok, dim = await _test_embedding("http://fake:11434", "nomic-embed-text", _client=client)
            assert ok is False
            assert dim == 0
        finally:
            await client.aclose()

    @pytest.mark.asyncio
    async def test_connect_error(self):
        def handler(req):
            raise httpx.ConnectError("refused")

        client = _mock_client(handler)
        try:
            ok, dim = await _test_embedding("http://fake:11434", "test-model", _client=client)
            assert ok is False
            assert dim == 0
        finally:
            await client.aclose()


class TestFindCurrentProfile:
    def test_known_model(self):
        assert _find_current_profile("nomic-embed-text") == "standard"
        assert _find_current_profile("all-minilm") == "lightweight"

    def test_unknown_model(self):
        assert _find_current_profile("custom-model") is None


class TestRunDoctor:
    @pytest.mark.asyncio
    async def test_ollama_not_installed(self):
        config = Config()
        with patch("index1.doctor.shutil.which", return_value=None):
            results = await run_doctor(config, fix=False)

        assert len(results) == 5
        assert results[0].status == "fail"
        assert results[0].name == "Ollama 已安装"
        for r in results[1:]:
            assert r.status == "skip"

    @pytest.mark.asyncio
    async def test_all_pass(self):
        config = Config()

        with (
            patch("index1.doctor.shutil.which", return_value="/usr/bin/ollama"),
            patch("index1.doctor._get_ollama_version", return_value="0.15.4"),
            patch("index1.doctor._check_service", return_value=True),
            patch("index1.doctor._check_model", return_value=True),
            patch("index1.doctor._test_embedding", return_value=(True, 768)),
            patch("index1.doctor.auto_select_model", return_value="standard"),
        ):
            results = await run_doctor(config, fix=False)

        # Check 1-5 核心检查应全部 pass，Check 6（中文支持）允许 warn（信息性提示）
        assert all(r.status in ("pass", "warn") for r in results)
        assert not any(r.status == "fail" for r in results)

    @pytest.mark.asyncio
    async def test_service_down_no_fix(self):
        config = Config()

        with (
            patch("index1.doctor.shutil.which", return_value="/usr/bin/ollama"),
            patch("index1.doctor._get_ollama_version", return_value="0.15.4"),
            patch("index1.doctor._check_service", return_value=False),
        ):
            results = await run_doctor(config, fix=False)

        assert results[0].status == "pass"  # installed
        assert results[1].status == "fail"  # service down
        for r in results[2:4]:
            assert r.status == "skip"

    @pytest.mark.asyncio
    async def test_model_missing_no_fix(self):
        config = Config()

        with (
            patch("index1.doctor.shutil.which", return_value="/usr/bin/ollama"),
            patch("index1.doctor._get_ollama_version", return_value="0.15.4"),
            patch("index1.doctor._check_service", return_value=True),
            patch("index1.doctor._check_model", return_value=False),
            patch("index1.doctor.auto_select_model", return_value="standard"),
        ):
            results = await run_doctor(config, fix=False)

        assert results[2].status == "fail"  # model missing
        assert results[3].status == "skip"  # embed test skipped

    @pytest.mark.asyncio
    async def test_dim_mismatch_warning(self):
        config = Config(embedding_dim=384)

        with (
            patch("index1.doctor.shutil.which", return_value="/usr/bin/ollama"),
            patch("index1.doctor._get_ollama_version", return_value="0.15.4"),
            patch("index1.doctor._check_service", return_value=True),
            patch("index1.doctor._check_model", return_value=True),
            patch("index1.doctor._test_embedding", return_value=(True, 768)),
            patch("index1.doctor.auto_select_model", return_value="standard"),
        ):
            results = await run_doctor(config, fix=False)

        assert results[3].status == "warn"  # dim mismatch
        assert "384" in results[3].message
