"""Tests for Qwen-0.5B Tier 3 reasoning router."""

from __future__ import annotations

from unittest.mock import AsyncMock, MagicMock, patch

import httpx
import pytest

from llmhosts.router.qwen import (
    QWEN_AVAILABLE,
    QWEN_MODEL,
    ROUTING_PROMPT,
    QwenRouter,
    QwenRoutingResult,
)

# -------------------------------------------------------------------
# Initialization
# -------------------------------------------------------------------


@pytest.mark.unit
def test_qwen_router_initialization() -> None:
    """QwenRouter initializes with default Ollama host."""
    router = QwenRouter()
    assert router._host == "http://localhost:11434"
    assert router._model == QWEN_MODEL
    assert router._available is False


@pytest.mark.unit
def test_qwen_router_custom_host() -> None:
    """QwenRouter accepts custom Ollama host."""
    router = QwenRouter(ollama_host="http://192.168.1.10:11434")
    assert router._host == "http://192.168.1.10:11434"


@pytest.mark.unit
def test_qwen_available_flag() -> None:
    """QWEN_AVAILABLE is True (module functional with httpx)."""
    assert QWEN_AVAILABLE is True


# -------------------------------------------------------------------
# Routing prompt format
# -------------------------------------------------------------------


@pytest.mark.unit
def test_routing_prompt_format() -> None:
    """ROUTING_PROMPT correctly interpolates available_models and user_request."""
    models = ["llama3.2:8b", "mistral:7b", "qwen2.5:7b"]
    request = "Write a Python function to sort a list."
    result = ROUTING_PROMPT.format(available_models=", ".join(models), user_request=request)
    assert "llama3.2:8b" in result
    assert "mistral:7b" in result
    assert "Write a Python function" in result
    assert "task_type" in result
    assert "recommended_tier" in result
    assert "privacy_sensitive" in result


# -------------------------------------------------------------------
# Result model fields
# -------------------------------------------------------------------


@pytest.mark.unit
def test_result_model_fields() -> None:
    """QwenRoutingResult has all required fields."""
    result = QwenRoutingResult(
        task_type="code",
        complexity="simple",
        recommended_tier="local_small",
        recommended_model="qwen2.5:7b",
        reasoning="Code completion task, local model sufficient",
        privacy_sensitive=False,
        confidence=0.9,
        latency_ms=120.0,
    )
    assert result.task_type == "code"
    assert result.complexity == "simple"
    assert result.recommended_tier == "local_small"
    assert result.recommended_model == "qwen2.5:7b"
    assert result.privacy_sensitive is False
    assert result.confidence == 0.9
    assert result.latency_ms == 120.0
    assert result.tier == "qwen"


# -------------------------------------------------------------------
# route() with mocked Ollama - success cases
# -------------------------------------------------------------------


@pytest.mark.unit
@pytest.mark.asyncio
async def test_qwen_route_code_completion() -> None:
    """Qwen router returns code-completion recommendation when mocked with code response."""
    mock_response = {
        "response": '{"task_type": "code", "complexity": "simple", '
        '"recommended_tier": "local_small", "recommended_model": "qwen2.5-coder:3b", '
        '"reasoning": "Code completion task, small local model sufficient", '
        '"privacy_sensitive": false}',
    }

    with patch("llmhosts.router.qwen.httpx.AsyncClient") as mock_client_cls:
        mock_client = MagicMock()
        mock_client.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client.__aexit__ = AsyncMock(return_value=None)
        mock_resp = MagicMock()
        mock_resp.json.return_value = mock_response
        mock_resp.raise_for_status = MagicMock()
        mock_client.post = AsyncMock(return_value=mock_resp)
        mock_client_cls.return_value = mock_client

        router = QwenRouter(ollama_host="http://localhost:11434")
        result = await router.route(
            prompt="Complete this function: def fib(n):",
            available_models=["qwen2.5-coder:3b", "llama3.2:8b", "mistral:7b"],
        )

    assert result.task_type == "code"
    assert result.complexity == "simple"
    assert result.recommended_tier == "local_small"
    assert "qwen" in result.recommended_model.lower()
    assert result.privacy_sensitive is False
    assert result.confidence > 0.5
    assert result.latency_ms >= 0
    assert result.tier == "qwen"


@pytest.mark.unit
@pytest.mark.asyncio
async def test_qwen_route_complex_research() -> None:
    """Qwen router returns complex/research recommendation when mocked."""
    mock_response = {
        "response": '{"task_type": "research", "complexity": "complex", '
        '"recommended_tier": "cloud", "recommended_model": "gpt-4o", '
        '"reasoning": "Multi-step research requires strong reasoning", '
        '"privacy_sensitive": false}',
    }

    with patch("llmhosts.router.qwen.httpx.AsyncClient") as mock_client_cls:
        mock_client = MagicMock()
        mock_client.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client.__aexit__ = AsyncMock(return_value=None)
        mock_resp = MagicMock()
        mock_resp.json.return_value = mock_response
        mock_resp.raise_for_status = MagicMock()
        mock_client.post = AsyncMock(return_value=mock_resp)
        mock_client_cls.return_value = mock_client

        router = QwenRouter()
        result = await router.route(
            prompt="Compare and contrast quantum computing approaches for optimization.",
            available_models=["gpt-4o", "claude-3-5-sonnet", "llama3.1:70b-q4"],
        )

    assert result.task_type == "research"
    assert result.complexity == "complex"
    assert result.recommended_tier == "cloud"
    assert result.confidence > 0.5


@pytest.mark.unit
@pytest.mark.asyncio
async def test_qwen_route_privacy_sensitive() -> None:
    """Qwen router detects PII and sets privacy_sensitive when mocked."""
    mock_response = {
        "response": '{"task_type": "analysis", "complexity": "moderate", '
        '"recommended_tier": "local_large", "recommended_model": "llama3.1:13b", '
        '"reasoning": "Medical record analysis - keep data local", '
        '"privacy_sensitive": true}',
    }

    with patch("llmhosts.router.qwen.httpx.AsyncClient") as mock_client_cls:
        mock_client = MagicMock()
        mock_client.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client.__aexit__ = AsyncMock(return_value=None)
        mock_resp = MagicMock()
        mock_resp.json.return_value = mock_response
        mock_resp.raise_for_status = MagicMock()
        mock_client.post = AsyncMock(return_value=mock_resp)
        mock_client_cls.return_value = mock_client

        router = QwenRouter()
        result = await router.route(
            prompt="Analyze this patient record: SSN 123-45-6789, DOB 01/15/1980",
            available_models=["llama3.1:13b", "gpt-4o", "mistral:7b"],
        )

    assert result.privacy_sensitive is True
    assert result.recommended_tier == "local_large"


# -------------------------------------------------------------------
# Fallback cases
# -------------------------------------------------------------------


@pytest.mark.unit
@pytest.mark.asyncio
async def test_qwen_route_fallback_unavailable() -> None:
    """When Ollama is down, return uncertain result (confidence=0)."""
    with patch("llmhosts.router.qwen.httpx.AsyncClient") as mock_client_cls:
        mock_client = MagicMock()
        mock_client.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client.__aexit__ = AsyncMock(return_value=None)
        mock_client.post = AsyncMock(side_effect=httpx.ConnectError("Connection refused"))
        mock_client_cls.return_value = mock_client

        router = QwenRouter()
        result = await router.route(
            prompt="Hello",
            available_models=["llama3.2:8b"],
        )

    assert result.confidence == 0.0
    assert "Ollama" in result.reasoning or "escalat" in result.reasoning.lower()
    assert result.recommended_tier == "cloud"


@pytest.mark.unit
@pytest.mark.asyncio
async def test_qwen_route_timeout_handling() -> None:
    """When Ollama times out, return uncertain result."""
    with patch("llmhosts.router.qwen.httpx.AsyncClient") as mock_client_cls:
        mock_client = MagicMock()
        mock_client.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client.__aexit__ = AsyncMock(return_value=None)
        mock_client.post = AsyncMock(side_effect=httpx.TimeoutException("timeout"))
        mock_client_cls.return_value = mock_client

        router = QwenRouter()
        result = await router.route(
            prompt="Hello",
            available_models=["llama3.2:8b"],
        )

    assert result.confidence == 0.0
    assert "timeout" in result.reasoning.lower() or "escalat" in result.reasoning.lower()


@pytest.mark.unit
@pytest.mark.asyncio
async def test_qwen_route_json_parse_failure() -> None:
    """When response is not valid JSON, use best-effort extraction."""
    mock_response = {
        "response": "Here is my analysis. task_type: code. recommended_model: llama3.2:8b. "
        'recommended_tier: "local_small". The user wants code help.',
    }

    with patch("llmhosts.router.qwen.httpx.AsyncClient") as mock_client_cls:
        mock_client = MagicMock()
        mock_client.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client.__aexit__ = AsyncMock(return_value=None)
        mock_resp = MagicMock()
        mock_resp.json.return_value = mock_response
        mock_resp.raise_for_status = MagicMock()
        mock_client.post = AsyncMock(return_value=mock_resp)
        mock_client_cls.return_value = mock_client

        router = QwenRouter()
        result = await router.route(
            prompt="Write a hello world",
            available_models=["llama3.2:8b", "mistral:7b"],
        )

    # Best-effort may extract some fields; confidence should be lower
    assert result.confidence <= 0.7
    assert result.tier == "qwen"
    # Should have a recommended model (from available_models if extraction fails)
    assert result.recommended_model in ["llama3.2:8b", "mistral:7b"] or "llama" in result.recommended_model.lower()


@pytest.mark.unit
@pytest.mark.asyncio
async def test_qwen_route_empty_models() -> None:
    """When available_models is empty, return uncertain without calling Ollama."""
    router = QwenRouter()
    result = await router.route(prompt="Hello", available_models=[])

    assert result.confidence == 0.0
    assert result.recommended_model == ""
    assert "No available models" in result.reasoning or "available" in result.reasoning.lower()


# -------------------------------------------------------------------
# JSON extraction edge cases
# -------------------------------------------------------------------


@pytest.mark.unit
@pytest.mark.asyncio
async def test_qwen_route_json_in_markdown_block() -> None:
    """Response with ```json ... ``` block is parsed correctly."""
    mock_response = {
        "response": '```json\n{"task_type": "chat", "complexity": "simple", '
        '"recommended_tier": "local_small", "recommended_model": "mistral:7b", '
        '"reasoning": "Simple chat", "privacy_sensitive": false}\n```',
    }

    with patch("llmhosts.router.qwen.httpx.AsyncClient") as mock_client_cls:
        mock_client = MagicMock()
        mock_client.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client.__aexit__ = AsyncMock(return_value=None)
        mock_resp = MagicMock()
        mock_resp.json.return_value = mock_response
        mock_resp.raise_for_status = MagicMock()
        mock_client.post = AsyncMock(return_value=mock_resp)
        mock_client_cls.return_value = mock_client

        router = QwenRouter()
        result = await router.route(
            prompt="Hi there",
            available_models=["mistral:7b", "llama3.2:8b"],
        )

    assert result.task_type == "chat"
    assert result.confidence > 0.5


# -------------------------------------------------------------------
# initialize() and ensure_model()
# -------------------------------------------------------------------


@pytest.mark.unit
@pytest.mark.asyncio
async def test_qwen_initialize_model_available() -> None:
    """initialize() sets _available=True when Qwen is in Ollama."""
    mock_tags = {
        "models": [{"name": "qwen2.5:0.5b"}, {"name": "llama3.2:8b"}],
    }

    with patch("llmhosts.router.qwen.httpx.AsyncClient") as mock_client_cls:
        mock_client = MagicMock()
        mock_client.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client.__aexit__ = AsyncMock(return_value=None)
        mock_resp = MagicMock()
        mock_resp.json.return_value = mock_tags
        mock_resp.raise_for_status = MagicMock()
        mock_client.get = AsyncMock(return_value=mock_resp)
        mock_client_cls.return_value = mock_client

        router = QwenRouter()
        ok = await router.initialize()

    assert ok is True
    assert router._available is True


@pytest.mark.unit
@pytest.mark.asyncio
async def test_qwen_initialize_model_not_available() -> None:
    """initialize() sets _available=False when Qwen is not in Ollama."""
    mock_tags = {"models": [{"name": "llama3.2:8b"}, {"name": "mistral:7b"}]}

    with patch("llmhosts.router.qwen.httpx.AsyncClient") as mock_client_cls:
        mock_client = MagicMock()
        mock_client.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client.__aexit__ = AsyncMock(return_value=None)
        mock_resp = MagicMock()
        mock_resp.json.return_value = mock_tags
        mock_resp.raise_for_status = MagicMock()
        mock_client.get = AsyncMock(return_value=mock_resp)
        mock_client_cls.return_value = mock_client

        router = QwenRouter()
        ok = await router.initialize()

    assert ok is False
    assert router._available is False


@pytest.mark.unit
@pytest.mark.asyncio
async def test_qwen_ensure_model() -> None:
    """ensure_model() returns True when model is available after initialize."""
    mock_tags = {"models": [{"name": "qwen2.5:0.5b"}]}

    with patch("llmhosts.router.qwen.httpx.AsyncClient") as mock_client_cls:
        mock_client = MagicMock()
        mock_client.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client.__aexit__ = AsyncMock(return_value=None)
        mock_resp = MagicMock()
        mock_resp.json.return_value = mock_tags
        mock_resp.raise_for_status = MagicMock()
        mock_client.get = AsyncMock(return_value=mock_resp)
        mock_client_cls.return_value = mock_client

        router = QwenRouter()
        ok = await router.ensure_model()

    assert ok is True
