"""Deterministic integration coverage for consultation fallback behavior."""

from __future__ import annotations

from dataclasses import replace
from pathlib import Path
from typing import Dict, List
from unittest.mock import patch

from foundry_mcp.core.ai_consultation import (
    ConsultationOrchestrator,
    ConsultationRequest,
    ConsultationWorkflow,
    ProviderResponse,
    ResultCache,
)
from foundry_mcp.core.llm_config.consultation import ConsultationConfig, WorkflowConsultationConfig
from foundry_mcp.core.providers import ProviderResult, ProviderStatus


class _FakeProvider:
    def __init__(self, responses: List[ProviderResult]) -> None:
        self._responses = list(responses)
        self.calls = 0

    def generate(self, _request) -> ProviderResult:
        self.calls += 1
        if self._responses:
            return self._responses.pop(0)
        return ProviderResult(
            content="",
            status=ProviderStatus.ERROR,
            provider_id="unknown",
            model_used="unknown",
            stderr="exhausted fake responses",
        )


def _result(
    *,
    provider_id: str,
    success: bool,
    content: str = "",
    error: str = "simulated failure",
) -> ProviderResult:
    return ProviderResult(
        content=content if success else "",
        status=ProviderStatus.SUCCESS if success else ProviderStatus.ERROR,
        provider_id=provider_id,
        model_used=f"{provider_id}-model",
        stderr=None if success else error,
    )


def _make_orchestrator(
    tmp_path: Path,
    *,
    priority: List[str],
    max_retries: int = 0,
    workflows: Dict[str, WorkflowConsultationConfig] | None = None,
) -> ConsultationOrchestrator:
    priority_specs = [
        entry if entry.startswith("[") else f"[cli]{entry}"
        for entry in priority
    ]
    config = ConsultationConfig(
        priority=priority_specs,
        default_timeout=30.0,
        max_retries=max_retries,
        retry_delay=0.0,
        fallback_enabled=True,
        cache_ttl=60,
        workflows=workflows or {},
    )
    orchestrator = ConsultationOrchestrator(
        cache=ResultCache(base_dir=tmp_path / "cache", default_ttl=60),
        config=config,
    )
    orchestrator._build_prompt = lambda _request: "test prompt"  # type: ignore[method-assign]
    return orchestrator


def test_single_model_fallback_respects_priority_order(tmp_path):
    orchestrator = _make_orchestrator(
        tmp_path,
        priority=["provider-a", "provider-b", "provider-c"],
        max_retries=0,
    )
    request = ConsultationRequest(
        workflow=ConsultationWorkflow.PLAN_REVIEW,
        prompt_id="TEST_PROMPT",
        context={},
    )

    providers = {
        "provider-a": _FakeProvider([
            _result(provider_id="provider-a", success=False, error="rate limit"),
        ]),
        "provider-b": _FakeProvider([
            _result(provider_id="provider-b", success=True, content="fallback success"),
        ]),
        "provider-c": _FakeProvider([
            _result(provider_id="provider-c", success=True, content="should not run"),
        ]),
    }
    resolve_order: List[str] = []

    def _resolve(provider_id, **_kwargs):
        resolve_order.append(provider_id)
        return providers[provider_id]

    with patch(
        "foundry_mcp.core.ai_consultation.orchestrator.check_provider_available",
        return_value=True,
    ), patch(
        "foundry_mcp.core.ai_consultation.orchestrator.resolve_provider",
        side_effect=_resolve,
    ):
        outcome = orchestrator.consult(request, use_cache=False)

    assert outcome.error is None
    assert outcome.provider_id == "provider-b"
    assert outcome.content == "fallback success"
    assert resolve_order == ["provider-a", "provider-b"]
    assert providers["provider-c"].calls == 0


def test_single_model_retries_before_fallback(tmp_path):
    orchestrator = _make_orchestrator(
        tmp_path,
        priority=["provider-a", "provider-b"],
        max_retries=2,
    )
    request = ConsultationRequest(
        workflow=ConsultationWorkflow.PLAN_REVIEW,
        prompt_id="TEST_PROMPT",
        context={},
    )

    providers = {
        "provider-a": _FakeProvider([
            _result(provider_id="provider-a", success=False, error="timed out"),
            _result(provider_id="provider-a", success=False, error="timed out"),
            _result(provider_id="provider-a", success=True, content="retry success"),
        ]),
        "provider-b": _FakeProvider([
            _result(provider_id="provider-b", success=True, content="fallback success"),
        ]),
    }
    resolve_order: List[str] = []

    def _resolve(provider_id, **_kwargs):
        resolve_order.append(provider_id)
        return providers[provider_id]

    with patch(
        "foundry_mcp.core.ai_consultation.orchestrator.check_provider_available",
        return_value=True,
    ), patch(
        "foundry_mcp.core.ai_consultation.orchestrator.resolve_provider",
        side_effect=_resolve,
    ):
        outcome = orchestrator.consult(request, use_cache=False)

    assert outcome.error is None
    assert outcome.provider_id == "provider-a"
    assert outcome.content == "retry success"
    assert providers["provider-a"].calls == 3
    assert providers["provider-b"].calls == 0
    assert resolve_order == ["provider-a", "provider-a", "provider-a"]


def test_parallel_fallback_stops_once_min_models_met(tmp_path):
    workflows = {"fidelity_review": WorkflowConsultationConfig(min_models=2)}
    orchestrator = _make_orchestrator(
        tmp_path,
        priority=["provider-a", "provider-b", "provider-c", "provider-d"],
        workflows=workflows,
    )
    request = ConsultationRequest(
        workflow=ConsultationWorkflow.FIDELITY_REVIEW,
        prompt_id="TEST_PROMPT",
        context={},
    )

    responses = {
        "provider-a": ProviderResponse(
            provider_id="provider-a",
            model_used="provider-a-model",
            content="primary success",
            success=True,
        ),
        "provider-b": ProviderResponse(
            provider_id="provider-b",
            model_used="provider-b-model",
            content="",
            success=False,
            error="simulated failure",
        ),
        "provider-c": ProviderResponse(
            provider_id="provider-c",
            model_used="provider-c-model",
            content="fallback success",
            success=True,
        ),
        "provider-d": ProviderResponse(
            provider_id="provider-d",
            model_used="provider-d-model",
            content="should not run",
            success=True,
        ),
    }
    call_order: List[str] = []

    async def _fake_execute(request, prompt, resolved):
        call_order.append(resolved.provider_id)
        return replace(responses[resolved.provider_id])

    orchestrator._execute_single_provider_async = _fake_execute  # type: ignore[method-assign]

    with patch(
        "foundry_mcp.core.ai_consultation.orchestrator.check_provider_available",
        return_value=True,
    ):
        outcome = orchestrator.consult(request, use_cache=False)

    assert [r.provider_id for r in outcome.responses] == [
        "provider-a",
        "provider-b",
        "provider-c",
    ]
    assert sum(1 for r in outcome.responses if r.success and r.content) == 2
    assert "provider-d" not in call_order
    assert any("attempting fallback for 1 more" in w for w in outcome.warnings)
