"""Tests for Hive karma wiring: dispatcher records karma, negative-karma block."""

from __future__ import annotations

from unittest.mock import AsyncMock

import pytest

from llmhosts.hive.karma import KarmaEngine
from llmhosts.hive.manager import HiveManager
from llmhosts.proxy.dispatcher import (
    COMPUTE_HOURS_PER_REQUEST,
    KARMA_THRESHOLD_BLOCK,
    BackendDispatcher,
    BackendEntry,
)
from llmhosts.proxy.errors import KarmaTooLowError
from llmhosts.proxy.models import UnifiedMessage, UnifiedRequest, UnifiedResponse


@pytest.fixture()
def hive_dir(tmp_path):
    d = tmp_path / ".llmhosts"
    d.mkdir()
    return d


@pytest.fixture()
async def manager_with_hive(hive_dir):
    registry = hive_dir / "hive_registry"
    mgr = HiveManager(hive_dir, registry_path=registry)
    await mgr.initialize()
    await mgr.create_hive("Karma Test Hive", "Alice")
    return mgr


@pytest.mark.asyncio
@pytest.mark.unit
async def test_dispatch_records_contribution_when_local(
    manager_with_hive: HiveManager,
) -> None:
    """When dispatch uses local Core backend, record_contribution is applied."""
    dispatcher = BackendDispatcher()
    await dispatcher.startup()
    dispatcher._backends.append(
        BackendEntry(
            name="llmhosts-core",
            base_url="local://",
            backend_type="llmhosts-core",
            models=["llama3.2"],
            priority=-1,
            healthy=True,
        )
    )
    # Set up mock Core engine
    mock_engine = type("MockEngine", (), {"is_loaded": True})()
    dispatcher._core_engine = mock_engine

    karma_engine = KarmaEngine(manager_with_hive)
    dispatcher.attach_hive_karma(manager_with_hive, karma_engine)

    _core_body = {
        "id": "chatcmpl-test",
        "object": "chat.completion",
        "created": 1000000,
        "model": "llama3.2",
        "choices": [{"index": 0, "message": {"role": "assistant", "content": "ok"}, "finish_reason": "stop"}],
        "usage": {"prompt_tokens": 10, "completion_tokens": 90, "total_tokens": 100},
    }
    _core_headers = {"X-LLMHosts-Backend": "llmhosts-core", "X-LLMHosts-Cost": "0.0"}

    dispatcher._dispatch_core = AsyncMock(return_value=(_core_body, _core_headers))

    req = UnifiedRequest(
        model="llama3.2",
        messages=[UnifiedMessage(role="user", content="Hi")],
    )
    await dispatcher.dispatch(req)

    hive = await manager_with_hive.get_hive()
    assert hive is not None
    member_id = manager_with_hive.get_current_member_id()
    assert member_id is not None
    member = next((m for m in hive.members if m.id == member_id), None)
    assert member is not None
    assert member.compute_contributed_hours >= COMPUTE_HOURS_PER_REQUEST
    await dispatcher.shutdown()


@pytest.mark.asyncio
@pytest.mark.unit
async def test_dispatch_records_consumption_when_remote(
    manager_with_hive: HiveManager,
) -> None:
    """When dispatch uses a remote (LAN) backend, record_consumption is applied."""
    dispatcher = BackendDispatcher()
    await dispatcher.startup()
    # Core is NOT loaded -- so dispatch falls through to the LAN backend
    dispatcher._core_engine = None
    dispatcher._backends.append(
        BackendEntry(
            name="lan-peer",
            base_url="http://192.168.1.10:11434",
            backend_type="openai",  # LAN nodes use OpenAI-compatible API
            models=["llama3.2"],
            priority=5,
            healthy=True,
        )
    )
    karma_engine = KarmaEngine(manager_with_hive)
    dispatcher.attach_hive_karma(manager_with_hive, karma_engine)

    async def _mock_dispatch_openai_compat(backend: BackendEntry, request: UnifiedRequest) -> UnifiedResponse:
        return UnifiedResponse(content="ok", model=request.model)

    dispatcher._dispatch_openai_compat = AsyncMock(side_effect=_mock_dispatch_openai_compat)

    req = UnifiedRequest(
        model="llama3.2",
        messages=[UnifiedMessage(role="user", content="Hi")],
    )
    await dispatcher.dispatch(req)

    hive = await manager_with_hive.get_hive()
    assert hive is not None
    member_id = manager_with_hive.get_current_member_id()
    assert member_id is not None
    member = next((m for m in hive.members if m.id == member_id), None)
    assert member is not None
    assert member.compute_consumed_hours >= COMPUTE_HOURS_PER_REQUEST
    await dispatcher.shutdown()


@pytest.mark.asyncio
@pytest.mark.unit
async def test_dispatch_raises_429_when_karma_below_threshold(
    manager_with_hive: HiveManager,
) -> None:
    """When current member karma < KARMA_THRESHOLD_BLOCK, dispatch raises KarmaTooLowError."""
    karma_engine = KarmaEngine(manager_with_hive)
    member_id = manager_with_hive.get_current_member_id()
    assert member_id is not None
    await karma_engine.record_consumption(member_id, 10.0)
    await karma_engine.record_contribution(member_id, 0.1)
    karma = await karma_engine.get_karma(member_id)
    assert karma < KARMA_THRESHOLD_BLOCK

    dispatcher = BackendDispatcher()
    await dispatcher.startup()
    dispatcher._backends.append(
        BackendEntry(
            name="llmhosts-core",
            base_url="local://",
            backend_type="llmhosts-core",
            models=["llama3.2"],
            priority=-1,
            healthy=True,
        )
    )
    dispatcher.attach_hive_karma(manager_with_hive, karma_engine)

    req = UnifiedRequest(
        model="llama3.2",
        messages=[UnifiedMessage(role="user", content="Hi")],
    )
    with pytest.raises(KarmaTooLowError) as exc_info:
        await dispatcher.dispatch(req)
    assert exc_info.value.status_code == 429
    assert exc_info.value.retry_after_seconds == 60
    await dispatcher.shutdown()


@pytest.mark.asyncio
@pytest.mark.unit
async def test_select_backend_sorts_by_karma_when_priority_order_given() -> None:
    """_select_backend prefers backends whose member_id has higher karma when priority_order is set."""
    dispatcher = BackendDispatcher()
    dispatcher._backends = [
        BackendEntry(
            name="lan-low",
            base_url="http://10.0.0.2:11434",
            backend_type="ollama",
            models=["llama3.2"],
            priority=5,
            healthy=True,
            member_id="member-low",
        ),
        BackendEntry(
            name="lan-high",
            base_url="http://10.0.0.1:11434",
            backend_type="ollama",
            models=["llama3.2"],
            priority=5,
            healthy=True,
            member_id="member-high",
        ),
    ]
    priority_order = [("member-high", 2.0), ("member-low", 0.5)]
    selected = dispatcher._select_backend("llama3.2", priority_order=priority_order)
    assert selected.member_id == "member-high"
    assert selected.name == "lan-high"
