from __future__ import annotations

from dataclasses import dataclass
from types import SimpleNamespace
from typing import Any
from unittest.mock import AsyncMock

import pytest

from src.services import ai_control as ai_control_module
from src.services import ai_router as ai_router_module
from src.services import openclaw as openclaw_module
from src.services.ai_router import AIProviderEnum
from src.services.intent_classifier import IntentType
from src.services.openclaw import OpenClawService


@dataclass
class _FakeEndpoint:
    provider_name: str
    url: str = "http://example.test"


class _FakeRoute:
    def all_endpoints_in_order(self) -> list[_FakeEndpoint]:
        return [
            _FakeEndpoint("ollama_gcp_a"),
            _FakeEndpoint("ollama_gcp_b"),
            _FakeEndpoint("ollama_local"),
            _FakeEndpoint("gemini", ""),
        ]


class _FakeFailoverManager:
    def __init__(self) -> None:
        self.task_types: list[str] = []

    async def select_provider(self, task_type: str = "general") -> _FakeRoute:
        self.task_types.append(task_type)
        return _FakeRoute()


class _UnorderedFailoverManager:
    async def select_provider(self, task_type: str = "general") -> SimpleNamespace:
        return SimpleNamespace(
            all_endpoints_in_order=lambda: [
                _FakeEndpoint("ollama_local"),
                _FakeEndpoint("gemini"),
                _FakeEndpoint("ollama_gcp_b"),
                _FakeEndpoint("ollama_gcp_a"),
            ],
        )


class _FakeRouter:
    async def route(self, prompt: str, context: dict[str, Any]) -> SimpleNamespace:
        return SimpleNamespace(
            selected_provider=AIProviderEnum.GEMINI,
            fallback_chain=[
                (AIProviderEnum.OPENCLAW_NEMO, "nvidia"),
                (AIProviderEnum.CLAUDE, "claude"),
                (AIProviderEnum.OLLAMA, "qwen2.5:7b-instruct"),
            ],
            intent=IntentType.DIAGNOSE,
            routing_reason="high complexity would normally prefer cloud",
        )


class _FakeExecutor:
    def __init__(self) -> None:
        self.provider_order: list[str] | None = None

    async def execute(
        self,
        *,
        prompt: str,
        provider_order: list[str],
        context: dict[str, Any],
        cache_ttl: int,
        require_local: bool,
    ) -> SimpleNamespace:
        self.provider_order = provider_order
        return SimpleNamespace(
            raw_response='{"root_cause":"ok","suggested_action":"NO_ACTION"}',
            provider=provider_order[0],
            success=True,
            tokens=42,
            cost_usd=0.0,
            latency_ms=10.0,
        )


@pytest.mark.asyncio
async def test_alert_context_uses_ollama_lane_then_openclaw_nemo_before_gemini(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    fake_executor = _FakeExecutor()
    fake_failover = _FakeFailoverManager()

    monkeypatch.setattr(openclaw_module.settings, "USE_AI_ROUTER", True)
    monkeypatch.setattr(openclaw_module.settings, "MOCK_MODE", False)
    monkeypatch.setattr(openclaw_module.settings, "ALERT_AI_ALLOW_CLOUD_FALLBACK", True)
    monkeypatch.setattr(openclaw_module.settings, "ALERT_AI_ENFORCE_OLLAMA_FIRST", True)
    monkeypatch.setattr(ai_control_module, "get_ai_router_enabled", AsyncMock(return_value=None))
    monkeypatch.setattr(ai_control_module, "get_primary_provider", AsyncMock(return_value=None))
    monkeypatch.setattr(ai_control_module, "is_provider_disabled", AsyncMock(return_value=False))
    monkeypatch.setattr(ai_router_module, "get_ai_router", lambda: _FakeRouter())
    monkeypatch.setattr(ai_router_module, "get_ai_executor", lambda: fake_executor)
    monkeypatch.setattr(openclaw_module, "get_ollama_failover_manager", lambda: fake_failover)

    service = object.__new__(OpenClawService)
    result = await service._call_with_fallback(
        "diagnose alert",
        alert_context={
            "incident_id": "INC-1",
            "alertname": "HostHighCpuLoad",
            "target_resource": "node-exporter-110",
        },
    )

    assert result == (
        '{"root_cause":"ok","suggested_action":"NO_ACTION"}',
        "ollama_gcp_a",
        True,
        42,
        0.0,
    )
    assert fake_executor.provider_order == [
        "ollama_gcp_a",
        "ollama_gcp_b",
        "ollama_local",
        "openclaw_nemo",
        "gemini",
    ]
    assert fake_failover.task_types == ["diagnose"]


@pytest.mark.asyncio
async def test_alert_context_can_disable_cloud_backup_for_cost_stop(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    fake_executor = _FakeExecutor()
    fake_failover = _FakeFailoverManager()

    monkeypatch.setattr(openclaw_module.settings, "USE_AI_ROUTER", True)
    monkeypatch.setattr(openclaw_module.settings, "MOCK_MODE", False)
    monkeypatch.setattr(openclaw_module.settings, "ALERT_AI_ALLOW_CLOUD_FALLBACK", False)
    monkeypatch.setattr(openclaw_module.settings, "ALERT_AI_ENFORCE_OLLAMA_FIRST", True)
    monkeypatch.setattr(ai_control_module, "get_ai_router_enabled", AsyncMock(return_value=None))
    monkeypatch.setattr(ai_control_module, "get_primary_provider", AsyncMock(return_value=None))
    monkeypatch.setattr(ai_control_module, "is_provider_disabled", AsyncMock(return_value=False))
    monkeypatch.setattr(ai_router_module, "get_ai_router", lambda: _FakeRouter())
    monkeypatch.setattr(ai_router_module, "get_ai_executor", lambda: fake_executor)
    monkeypatch.setattr(openclaw_module, "get_ollama_failover_manager", lambda: fake_failover)

    service = object.__new__(OpenClawService)
    await service._call_with_fallback(
        "diagnose alert",
        alert_context={"incident_id": "INC-1", "alertname": "HostHighCpuLoad"},
    )

    assert fake_executor.provider_order == ["ollama_gcp_a", "ollama_gcp_b", "ollama_local"]


@pytest.mark.asyncio
async def test_non_alert_context_keeps_router_cloud_order(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    fake_executor = _FakeExecutor()

    monkeypatch.setattr(openclaw_module.settings, "USE_AI_ROUTER", True)
    monkeypatch.setattr(openclaw_module.settings, "MOCK_MODE", False)
    monkeypatch.setattr(openclaw_module.settings, "ALERT_AI_ALLOW_CLOUD_FALLBACK", False)
    monkeypatch.setattr(openclaw_module.settings, "ALERT_AI_ENFORCE_OLLAMA_FIRST", True)
    monkeypatch.setattr(ai_control_module, "get_ai_router_enabled", AsyncMock(return_value=None))
    monkeypatch.setattr(ai_control_module, "get_primary_provider", AsyncMock(return_value=None))
    monkeypatch.setattr(ai_control_module, "is_provider_disabled", AsyncMock(return_value=False))
    monkeypatch.setattr(ai_router_module, "get_ai_router", lambda: _FakeRouter())
    monkeypatch.setattr(ai_router_module, "get_ai_executor", lambda: fake_executor)

    service = object.__new__(OpenClawService)
    await service._call_with_fallback("general question", alert_context={"intent_hint": "query"})

    assert fake_executor.provider_order == ["gemini", "openclaw_nemo", "claude", "ollama"]


@pytest.mark.asyncio
async def test_explicit_ai_governance_context_uses_ollama_first(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    fake_executor = _FakeExecutor()
    fake_failover = _FakeFailoverManager()

    monkeypatch.setattr(openclaw_module.settings, "USE_AI_ROUTER", True)
    monkeypatch.setattr(openclaw_module.settings, "MOCK_MODE", False)
    monkeypatch.setattr(openclaw_module.settings, "ALERT_AI_ALLOW_CLOUD_FALLBACK", True)
    monkeypatch.setattr(openclaw_module.settings, "ALERT_AI_ENFORCE_OLLAMA_FIRST", True)
    monkeypatch.setattr(ai_control_module, "get_ai_router_enabled", AsyncMock(return_value=None))
    monkeypatch.setattr(ai_control_module, "get_primary_provider", AsyncMock(return_value=None))
    monkeypatch.setattr(ai_control_module, "is_provider_disabled", AsyncMock(return_value=False))
    monkeypatch.setattr(ai_router_module, "get_ai_router", lambda: _FakeRouter())
    monkeypatch.setattr(ai_router_module, "get_ai_executor", lambda: fake_executor)
    monkeypatch.setattr(openclaw_module, "get_ollama_failover_manager", lambda: fake_failover)

    service = object.__new__(OpenClawService)
    await service._call_with_fallback(
        "analyze config drift",
        alert_context={
            "intent_hint": "config",
            "task_type": "diagnose",
            "enforce_ollama_first": True,
            "allow_gcp_heavy_model": True,
            "target_resource": "config-drift",
        },
    )

    assert fake_executor.provider_order == [
        "ollama_gcp_a",
        "ollama_gcp_b",
        "ollama_local",
        "openclaw_nemo",
        "gemini",
    ]
    assert fake_failover.task_types == ["diagnose"]


@pytest.mark.asyncio
async def test_alert_context_uses_gcp_a_gcp_b_then_111_order(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    fake_failover = _FakeFailoverManager()

    monkeypatch.setattr(openclaw_module.settings, "ALERT_AI_ALLOW_CLOUD_FALLBACK", False)
    monkeypatch.setattr(openclaw_module.settings, "ALERT_AI_ENFORCE_OLLAMA_FIRST", True)
    monkeypatch.setattr(openclaw_module, "get_ollama_failover_manager", lambda: fake_failover)

    service = object.__new__(OpenClawService)
    provider_order = await service._resolve_alert_provider_order(
        task_type="diagnose",
        alert_context={"incident_id": "INC-1", "alertname": "HostHighCpuLoad"},
    )

    assert provider_order == ["ollama_gcp_a", "ollama_gcp_b", "ollama_local"]


@pytest.mark.asyncio
async def test_alert_context_sorts_ollama_lane_and_drops_cloud_providers(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setattr(openclaw_module.settings, "ALERT_AI_ALLOW_CLOUD_FALLBACK", False)
    monkeypatch.setattr(openclaw_module.settings, "ALERT_AI_ENFORCE_OLLAMA_FIRST", True)
    monkeypatch.setattr(openclaw_module, "get_ollama_failover_manager", lambda: _UnorderedFailoverManager())

    service = object.__new__(OpenClawService)
    provider_order = await service._resolve_alert_provider_order(
        task_type="diagnose",
        alert_context={"incident_id": "INC-1", "alertname": "HostHighCpuLoad"},
    )

    assert provider_order == ["ollama_gcp_a", "ollama_gcp_b", "ollama_local"]


@pytest.mark.asyncio
async def test_alert_context_sorts_ollama_lane_before_openclaw_nemo_backup(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setattr(openclaw_module.settings, "ALERT_AI_ALLOW_CLOUD_FALLBACK", True)
    monkeypatch.setattr(openclaw_module.settings, "ALERT_AI_ENFORCE_OLLAMA_FIRST", True)
    monkeypatch.setattr(ai_control_module, "is_provider_disabled", AsyncMock(return_value=False))
    monkeypatch.setattr(openclaw_module, "get_ollama_failover_manager", lambda: _UnorderedFailoverManager())

    service = object.__new__(OpenClawService)
    provider_order = await service._resolve_alert_provider_order(
        task_type="diagnose",
        alert_context={"incident_id": "INC-1", "alertname": "HostHighCpuLoad"},
        cloud_provider_order=["claude", "openclaw_nemo", "gemini", "ollama"],
    )

    assert provider_order == ["ollama_gcp_a", "ollama_gcp_b", "ollama_local", "openclaw_nemo", "gemini"]


@pytest.mark.asyncio
async def test_alert_context_respects_disabled_openclaw_nemo_backup(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setattr(openclaw_module.settings, "ALERT_AI_ALLOW_CLOUD_FALLBACK", True)
    monkeypatch.setattr(openclaw_module.settings, "ALERT_AI_ENFORCE_OLLAMA_FIRST", True)
    monkeypatch.setattr(ai_control_module, "is_provider_disabled", AsyncMock(return_value=True))
    monkeypatch.setattr(openclaw_module, "get_ollama_failover_manager", lambda: _UnorderedFailoverManager())

    service = object.__new__(OpenClawService)
    provider_order = await service._resolve_alert_provider_order(
        task_type="diagnose",
        alert_context={"incident_id": "INC-1", "alertname": "HostHighCpuLoad"},
        cloud_provider_order=["openclaw_nemo", "gemini"],
    )

    assert provider_order == ["ollama_gcp_a", "ollama_gcp_b", "ollama_local", "gemini"]
