# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT
"""Tests for delegation_orchestrator.py (OMN-2281).

Covers:
- Quality gate: pass/fail for each task type
- Handler endpoint selection: routing by intent, fallback when no endpoint
- Feature flag gating
- orchestrate_delegation: full success path, each failure mode
- Event emission: called on success and quality gate failure
"""

from __future__ import annotations

import sys
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, patch
from uuid import uuid4

import pytest
from pydantic import ValidationError

# Insert hooks/lib so delegation_orchestrator can be imported directly.
# Mirrors the pattern used by test_local_delegation_handler.py.
_HOOKS_LIB = (
    Path(__file__).parent.parent.parent.parent.parent
    / "plugins"
    / "onex"
    / "hooks"
    / "lib"
)
if str(_HOOKS_LIB) not in sys.path:
    sys.path.insert(0, str(_HOOKS_LIB))

import delegation_orchestrator as do  # noqa: E402 I001


# ---------------------------------------------------------------------------
# ModelTaskDelegatedPayload schema tests
# ---------------------------------------------------------------------------


def _valid_payload_kwargs() -> dict[str, Any]:
    """Return a minimal set of valid kwargs for ModelTaskDelegatedPayload."""
    from omniclaude.hooks.schemas import ModelTaskDelegatedPayload  # noqa: F401

    return {
        "session_id": "abc12345-1234-5678-abcd-1234567890ab",
        "correlation_id": uuid4(),
        "emitted_at": datetime(2025, 1, 15, 12, 0, 0, tzinfo=UTC),
        "task_type": "document",
        "handler_used": "doc_gen",
        "model_used": "Qwen2.5-72B",
        "quality_gate_passed": True,
        "quality_gate_reason": None,
        "delegation_success": True,
        "estimated_savings_usd": 0.0112,
        "latency_ms": 320,
    }


@pytest.mark.unit
class TestModelTaskDelegatedPayloadSchema:
    """Direct field-constraint tests for ModelTaskDelegatedPayload."""

    def test_valid_payload_construction(self) -> None:
        """Construct a valid payload with all required fields; assert it succeeds."""
        from omniclaude.hooks.schemas import ModelTaskDelegatedPayload

        kwargs = _valid_payload_kwargs()
        payload = ModelTaskDelegatedPayload(**kwargs)
        assert payload.task_type == "document"
        assert payload.handler_used == "doc_gen"
        assert payload.model_used == "Qwen2.5-72B"
        assert payload.delegation_success is True
        assert payload.estimated_savings_usd == pytest.approx(0.0112)
        assert payload.latency_ms == 320

    def test_frozen_model_raises_on_mutation(self) -> None:
        """Attempting to set an attribute on a frozen payload raises TypeError or ValidationError."""
        from omniclaude.hooks.schemas import ModelTaskDelegatedPayload

        payload = ModelTaskDelegatedPayload(**_valid_payload_kwargs())
        with pytest.raises((TypeError, ValidationError)):
            payload.task_type = "test"  # type: ignore[misc]

    def test_handler_used_min_length_enforced(self) -> None:
        """handler_used='' (empty string) must raise ValidationError (min_length=1)."""
        from omniclaude.hooks.schemas import ModelTaskDelegatedPayload

        kwargs = _valid_payload_kwargs()
        kwargs["handler_used"] = ""
        with pytest.raises(ValidationError):
            ModelTaskDelegatedPayload(**kwargs)

    def test_quality_gate_reason_max_length_enforced(self) -> None:
        """quality_gate_reason of 201 chars must raise ValidationError (max_length=200)."""
        from omniclaude.hooks.schemas import ModelTaskDelegatedPayload

        kwargs = _valid_payload_kwargs()
        kwargs["quality_gate_reason"] = "x" * 201
        with pytest.raises(ValidationError):
            ModelTaskDelegatedPayload(**kwargs)

    def test_emitted_at_gets_timezone_attached(self) -> None:
        """A naive datetime passed to emitted_at is coerced to UTC by TimezoneAwareDatetime.

        TimezoneAwareDatetime uses ensure_timezone_aware(assume_utc=True), which
        converts naive datetimes to UTC rather than rejecting them.  The resulting
        payload must be created successfully and emitted_at must carry tzinfo.
        """
        from omniclaude.hooks.schemas import ModelTaskDelegatedPayload

        kwargs = _valid_payload_kwargs()
        # Naive datetime has no tzinfo — TimezoneAwareDatetime coerces it to UTC.
        kwargs["emitted_at"] = datetime(2025, 1, 15, 12, 0, 0)
        payload = ModelTaskDelegatedPayload(**kwargs)
        assert payload.emitted_at.tzinfo is not None
        assert payload.emitted_at.tzinfo == UTC


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _make_score(
    delegatable: bool,
    confidence: float = 0.95,
    delegate_to_model: str = "qwen2.5-14b",
    estimated_savings_usd: float = 0.0112,
    reasons: list[str] | None = None,
) -> Any:
    """Build a minimal ModelDelegationScore-compatible mock object."""
    score = MagicMock()
    score.delegatable = delegatable
    score.confidence = confidence
    score.delegate_to_model = delegate_to_model
    score.estimated_savings_usd = estimated_savings_usd
    score.reasons = reasons or ["intent 'document' is in the delegation allow-list"]
    return score


def _make_context(primary_intent_value: str) -> Any:
    """Build a minimal TaskContext-compatible mock object."""
    ctx = MagicMock()
    intent = MagicMock()
    intent.value = primary_intent_value
    ctx.primary_intent = intent
    return ctx


def _make_classifier_mock(
    score: Any,
    intent_value: str,
) -> Any:
    """Build a TaskClassifier mock that returns the given score and context."""
    ctx = _make_context(intent_value)
    mock = MagicMock()
    mock.is_delegatable.return_value = score
    mock.classify.return_value = ctx
    return mock


# ---------------------------------------------------------------------------
# Quality gate tests
# ---------------------------------------------------------------------------


@pytest.mark.unit
class TestQualityGate:
    """_run_quality_gate correctly accepts and rejects responses per task type."""

    # --- DOCUMENT ---

    def test_document_passes_with_valid_response(self) -> None:
        """Doc response with docstring markers passes the gate."""
        response = (
            "def process(data: dict) -> str:\n"
            '    """Process the input data.\n\n'
            "    Args:\n"
            "        data: Input dictionary.\n\n"
            "    Returns:\n"
            "        str: Processed result.\n"
            '    """\n'
            "    return str(data)"
        )
        passed, reason = do._run_quality_gate(response, "document")
        assert passed is True
        assert reason == ""

    def test_document_fails_too_short(self) -> None:
        """Doc response shorter than 100 chars fails."""
        passed, reason = do._run_quality_gate("Short.", "document")
        assert passed is False
        assert "too short" in reason

    def test_document_fails_missing_markers(self) -> None:
        """Doc response of adequate length but no docstring markers fails."""
        # 100+ chars but no Args:/Returns:/'""' markers
        response = "x" * 120  # No markers at all
        passed, reason = do._run_quality_gate(response, "document")
        assert passed is False
        assert "missing expected markers" in reason

    def test_document_fails_error_indicator(self) -> None:
        """Doc response starting with refusal phrase fails."""
        response = (
            "I cannot provide documentation for this code as it "
            'contains sensitive information.\n\n"""Args:\n    x: value.\n"""'
        )
        passed, reason = do._run_quality_gate(response, "document")
        assert passed is False
        assert "refusal indicator" in reason

    # --- TEST ---

    def test_test_passes_with_pytest_function(self) -> None:
        """Test response with def test_ and assert passes."""
        response = (
            "import pytest\n\n"
            "def test_process_returns_string():\n"
            "    result = process({'key': 'value'})\n"
            "    assert isinstance(result, str)\n"
        )
        passed, reason = do._run_quality_gate(response, "test")
        assert passed is True
        assert reason == ""

    def test_test_passes_with_class_test(self) -> None:
        """Test response with class Test... structure passes."""
        response = (
            "@pytest.mark.unit\n"
            "class TestMyModule:\n"
            "    def test_something(self):\n"
            "        assert 1 + 1 == 2\n"
        )
        passed, reason = do._run_quality_gate(response, "test")
        assert passed is True
        assert reason == ""

    def test_test_fails_too_short(self) -> None:
        """Test response shorter than 80 chars fails."""
        passed, reason = do._run_quality_gate("def test_foo(): pass", "test")
        assert passed is False
        assert "too short" in reason

    def test_test_fails_no_test_markers(self) -> None:
        """Test response of adequate length but no test markers fails."""
        # 80+ chars, no def test_ / class Test / @pytest / assert
        response = "This is a test plan document that explains how to manually verify the system behavior by inspection.\n"
        passed, reason = do._run_quality_gate(response, "test")
        assert passed is False
        assert "missing expected markers" in reason

    def test_test_fails_error_indicator(self) -> None:
        """Test response with 'As an AI' in first 200 chars fails."""
        response = (
            "As an AI, I can help you write tests.\n"
            "def test_example():\n"
            "    assert True\n"
            "# " + "x" * 80
        )
        passed, reason = do._run_quality_gate(response, "test")
        assert passed is False
        assert "refusal indicator" in reason

    # --- RESEARCH ---

    def test_research_passes_with_adequate_response(self) -> None:
        """Research response >= 60 chars with no error indicators passes."""
        response = (
            "Kafka is a distributed event streaming platform that handles "
            "high-throughput data pipelines. It uses topics, producers, and consumers."
        )
        passed, reason = do._run_quality_gate(response, "research")
        assert passed is True
        assert reason == ""

    def test_research_fails_too_short(self) -> None:
        """Research response shorter than 60 chars fails."""
        passed, reason = do._run_quality_gate("Kafka is a tool.", "research")
        assert passed is False
        assert "too short" in reason

    def test_research_fails_error_indicator(self) -> None:
        """Research response starting with "I'm unable" fails."""
        response = (
            "I'm unable to answer questions about proprietary systems. "
            "Please consult the official documentation for more information."
        )
        passed, reason = do._run_quality_gate(response, "research")
        assert passed is False
        assert "refusal indicator" in reason

    def test_research_no_markers_required(self) -> None:
        """Research task does not require content markers (no entry in _TASK_MARKERS)."""
        # Research is not in _TASK_MARKERS so any 60+ char response without refusal passes
        response = "a" * 70
        passed, reason = do._run_quality_gate(response, "research")
        assert passed is True
        assert reason == ""

    # --- Unknown task type ---

    def test_unknown_task_type_uses_default_min_length(self) -> None:
        """Unknown task type falls back to 60-char minimum."""
        passed, _ = do._run_quality_gate("x" * 65, "unknown_task")
        assert passed is True

        passed, reason = do._run_quality_gate("x" * 50, "unknown_task")
        assert passed is False
        assert "too short" in reason


# ---------------------------------------------------------------------------
# Handler endpoint selection tests
# ---------------------------------------------------------------------------


@pytest.mark.unit
class TestSelectHandlerEndpoint:
    """_select_handler_endpoint returns correct metadata or None."""

    def test_document_routes_to_reasoning(self) -> None:
        """'document' intent routes to REASONING endpoint (doc_gen handler)."""
        mock_endpoint = MagicMock()
        mock_endpoint.url = "http://llm-reasoning-host:8101"
        mock_endpoint.model_name = "Qwen2.5-72B"

        mock_registry_instance = MagicMock()
        mock_registry_instance.get_endpoint.return_value = mock_endpoint

        with patch.object(
            do, "LocalLlmEndpointRegistry", return_value=mock_registry_instance
        ):
            with patch.object(do, "LlmEndpointPurpose") as mock_purpose_cls:
                mock_purpose_cls.return_value = MagicMock()
                result = do._select_handler_endpoint("document")

        assert result is not None
        _url, model_name, system_prompt, handler_name = result
        assert handler_name == "doc_gen"
        assert "documentation" in system_prompt.lower()
        assert model_name == "Qwen2.5-72B"

    def test_test_routes_to_code_analysis(self) -> None:
        """'test' intent routes to CODE_ANALYSIS endpoint (test_boilerplate handler)."""
        mock_endpoint = MagicMock()
        mock_endpoint.url = "http://llm-coder-host:8000"
        mock_endpoint.model_name = "Qwen3-Coder-30B"

        mock_registry_instance = MagicMock()
        mock_registry_instance.get_endpoint.return_value = mock_endpoint

        with patch.object(
            do, "LocalLlmEndpointRegistry", return_value=mock_registry_instance
        ):
            with patch.object(do, "LlmEndpointPurpose") as mock_purpose_cls:
                mock_purpose_cls.return_value = MagicMock()
                result = do._select_handler_endpoint("test")

        assert result is not None
        _, _, system_prompt, handler_name = result
        assert handler_name == "test_boilerplate"
        assert "pytest" in system_prompt.lower()

    def test_research_routes_to_code_analysis(self) -> None:
        """'research' intent routes to CODE_ANALYSIS endpoint (code_review handler)."""
        mock_endpoint = MagicMock()
        mock_endpoint.url = "http://llm-coder-host:8000"
        mock_endpoint.model_name = "Qwen3-Coder-30B"

        mock_registry_instance = MagicMock()
        mock_registry_instance.get_endpoint.return_value = mock_endpoint

        with patch.object(
            do, "LocalLlmEndpointRegistry", return_value=mock_registry_instance
        ):
            with patch.object(do, "LlmEndpointPurpose") as mock_purpose_cls:
                mock_purpose_cls.return_value = MagicMock()
                result = do._select_handler_endpoint("research")

        assert result is not None
        _, _, system_prompt, handler_name = result
        assert handler_name == "code_review"
        assert "review" in system_prompt.lower()

    def test_unknown_intent_returns_none(self) -> None:
        """Intent not in _HANDLER_ROUTING returns None without calling registry."""
        result = do._select_handler_endpoint("debug")
        assert result is None

    def test_no_endpoint_configured_returns_none(self) -> None:
        """Registry returning None endpoint -> _select_handler_endpoint returns None."""
        mock_registry_instance = MagicMock()
        mock_registry_instance.get_endpoint.return_value = None

        with patch.object(
            do, "LocalLlmEndpointRegistry", return_value=mock_registry_instance
        ):
            result = do._select_handler_endpoint("document")

        assert result is None

    def test_registry_exception_returns_none(self) -> None:
        """Exception from registry -> returns None, never raises."""
        with patch.object(
            do,
            "LocalLlmEndpointRegistry",
            side_effect=RuntimeError("registry unavailable"),
        ):
            result = do._select_handler_endpoint("test")

        assert result is None

    def test_unavailable_registry_returns_none(self) -> None:
        """When LocalLlmEndpointRegistry is None (import failed) -> returns None."""
        with patch.object(do, "LocalLlmEndpointRegistry", None):
            result = do._select_handler_endpoint("document")
        assert result is None

    def test_unavailable_purpose_returns_none(self) -> None:
        """When LlmEndpointPurpose is None (import failed) -> returns None."""
        with patch.object(do, "LlmEndpointPurpose", None):
            result = do._select_handler_endpoint("document")
        assert result is None


# ---------------------------------------------------------------------------
# Handler routing table contract test
# ---------------------------------------------------------------------------


@pytest.mark.unit
def test_handler_routing_purpose_names_match_enum() -> None:
    """Every purpose string in _HANDLER_ROUTING must match an LlmEndpointPurpose value.

    This acts as a contract check: if LlmEndpointPurpose enum values are
    renamed, the routing table must be updated accordingly.  A mismatch
    silently causes _select_handler_endpoint() to return None at runtime,
    so catching it in a test is essential.
    """
    LlmEndpointPurpose = pytest.importorskip(
        "omniclaude.config.model_local_llm_config",
        reason="omniclaude package not installed",
    ).LlmEndpointPurpose

    valid_purpose_values = {p.value for p in LlmEndpointPurpose}

    for intent_key, routing_tuple in do._HANDLER_ROUTING.items():
        purpose_name = routing_tuple[0]
        assert purpose_name in valid_purpose_values, (
            f"_HANDLER_ROUTING[{intent_key!r}] purpose {purpose_name!r} is not a "
            f"valid LlmEndpointPurpose value. Valid values: {sorted(valid_purpose_values)}"
        )


# ---------------------------------------------------------------------------
# Feature flag tests
# ---------------------------------------------------------------------------


@pytest.mark.unit
class TestFeatureFlags:
    """orchestrate_delegation respects ENABLE_LOCAL_DELEGATION kill-switch and LLM endpoint config.

    _is_delegation_enabled() uses connection-config-inference:
    - delegation is enabled when LLM_CODER_URL or LLM_DEEPSEEK_R1_URL are set
      (and ENABLE_LOCAL_DELEGATION != false)
    - ENABLE_LOCAL_DELEGATION=false is an explicit kill-switch regardless of LLM URLs
    - Legacy ENABLE_LOCAL_INFERENCE_PIPELINE is no longer required
    """

    def test_both_flags_off_returns_not_delegated(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        # No LLM endpoints configured → connection-config-inference returns disabled
        monkeypatch.delenv("ENABLE_LOCAL_INFERENCE_PIPELINE", raising=False)
        monkeypatch.delenv("ENABLE_LOCAL_DELEGATION", raising=False)
        monkeypatch.delenv("LLM_CODER_URL", raising=False)
        monkeypatch.delenv("LLM_DEEPSEEK_R1_URL", raising=False)
        result = do.orchestrate_delegation(
            prompt="document this function", correlation_id="corr-1"
        )
        assert result["delegated"] is False
        assert result.get("reason") == "feature_disabled"

    def test_only_parent_flag_set(self, monkeypatch: pytest.MonkeyPatch) -> None:
        # Legacy ENABLE_LOCAL_INFERENCE_PIPELINE alone does not enable delegation
        monkeypatch.setenv("ENABLE_LOCAL_INFERENCE_PIPELINE", "true")
        monkeypatch.delenv("ENABLE_LOCAL_DELEGATION", raising=False)
        monkeypatch.delenv("LLM_CODER_URL", raising=False)
        monkeypatch.delenv("LLM_DEEPSEEK_R1_URL", raising=False)
        result = do.orchestrate_delegation(
            prompt="document this", correlation_id="corr-2"
        )
        assert result["delegated"] is False
        assert result.get("reason") == "feature_disabled"

    def test_only_delegation_flag_set(self, monkeypatch: pytest.MonkeyPatch) -> None:
        # ENABLE_LOCAL_DELEGATION=true alone (no LLM URLs) → still disabled
        monkeypatch.delenv("ENABLE_LOCAL_INFERENCE_PIPELINE", raising=False)
        monkeypatch.setenv("ENABLE_LOCAL_DELEGATION", "true")
        monkeypatch.delenv("LLM_CODER_URL", raising=False)
        monkeypatch.delenv("LLM_DEEPSEEK_R1_URL", raising=False)
        result = do.orchestrate_delegation(
            prompt="document this", correlation_id="corr-3"
        )
        assert result["delegated"] is False
        assert result.get("reason") == "feature_disabled"

    def test_feature_disabled_emits_delegation_event(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """feature_disabled path emits a delegation event with delegation_success=False."""
        from uuid import uuid4

        monkeypatch.delenv("ENABLE_LOCAL_INFERENCE_PIPELINE", raising=False)
        monkeypatch.delenv("ENABLE_LOCAL_DELEGATION", raising=False)
        monkeypatch.delenv("LLM_CODER_URL", raising=False)
        monkeypatch.delenv("LLM_DEEPSEEK_R1_URL", raising=False)

        with patch.object(do, "_emit_delegation_event") as mock_emit:
            result = do.orchestrate_delegation(
                prompt="document this function",
                session_id="s-flags",
                correlation_id=str(uuid4()),
            )

        assert result["delegated"] is False
        assert result.get("reason") == "feature_disabled"

        mock_emit.assert_called_once()
        call_kwargs = mock_emit.call_args.kwargs
        assert call_kwargs.get("delegation_success") is False
        assert call_kwargs.get("quality_gate_passed") is False
        assert call_kwargs.get("quality_gate_reason") == "feature_disabled"
        assert call_kwargs.get("task_type") == "unknown"
        assert call_kwargs.get("handler_name") == "unknown"
        assert call_kwargs.get("savings_usd") == 0.0

    def test_both_flags_true_proceeds_past_feature_gate(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Both flags true -> proceeds to classification (not feature_disabled)."""
        monkeypatch.setenv("ENABLE_LOCAL_INFERENCE_PIPELINE", "true")
        monkeypatch.setenv("ENABLE_LOCAL_DELEGATION", "true")
        monkeypatch.setenv("LLM_CODER_URL", "http://localhost:8000")
        score = _make_score(False, reasons=["not delegatable"])
        classifier_mock = _make_classifier_mock(score, "debug")

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            result = do.orchestrate_delegation(
                prompt="fix the bug", correlation_id="corr-4"
            )
        assert result.get("reason") != "feature_disabled"

    def test_unavailable_classifier_returns_error(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """When TaskClassifier is None (import failed) -> classification_error."""
        monkeypatch.setenv("ENABLE_LOCAL_INFERENCE_PIPELINE", "true")
        monkeypatch.setenv("ENABLE_LOCAL_DELEGATION", "true")
        monkeypatch.setenv("LLM_CODER_URL", "http://localhost:8000")
        with patch.object(do, "TaskClassifier", None):
            result = do.orchestrate_delegation(
                prompt="document this", correlation_id="corr-null-cls"
            )
        assert result["delegated"] is False
        assert "classification_error" in result.get("reason", "")

    def test_unavailable_classifier_emits_delegation_event(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """When TaskClassifier is None, a delegation event is emitted with delegation_success=False."""
        from uuid import uuid4

        monkeypatch.setenv("ENABLE_LOCAL_INFERENCE_PIPELINE", "true")
        monkeypatch.setenv("ENABLE_LOCAL_DELEGATION", "true")
        monkeypatch.setenv("LLM_CODER_URL", "http://localhost:8000")

        with patch.object(do, "TaskClassifier", None):
            with patch.object(do, "_emit_delegation_event") as mock_emit:
                result = do.orchestrate_delegation(
                    prompt="document this",
                    session_id="s-null-cls",
                    correlation_id=str(uuid4()),
                )

        assert result["delegated"] is False
        assert "classification_error" in result.get("reason", "")

        mock_emit.assert_called_once()
        call_kwargs = mock_emit.call_args.kwargs
        assert call_kwargs.get("delegation_success") is False
        assert call_kwargs.get("quality_gate_passed") is False
        assert (call_kwargs.get("quality_gate_reason") or "").startswith(
            "classification_error:"
        )
        assert call_kwargs.get("task_type") == "unknown"
        assert call_kwargs.get("savings_usd") == 0.0


# ---------------------------------------------------------------------------
# Classification gate tests
# ---------------------------------------------------------------------------


@pytest.mark.unit
class TestClassificationGate:
    """orchestrate_delegation skips when classifier says not delegatable."""

    def _enable_flags(self, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.setenv("ENABLE_LOCAL_INFERENCE_PIPELINE", "true")
        monkeypatch.setenv("ENABLE_LOCAL_DELEGATION", "true")
        monkeypatch.setenv("LLM_CODER_URL", "http://localhost:8000")

    def test_not_delegatable_returns_false(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        self._enable_flags(monkeypatch)
        score = _make_score(
            False, confidence=0.3, reasons=["intent 'debug' not in allow-list"]
        )
        classifier_mock = _make_classifier_mock(score, "debug")

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            result = do.orchestrate_delegation(
                prompt="fix the bug", correlation_id="corr-10"
            )
        assert result["delegated"] is False
        assert "not in allow-list" in result.get("reason", "")

    def test_not_delegatable_emits_delegation_event(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """not-delegatable path emits a delegation event with delegation_success=False."""
        from uuid import uuid4

        self._enable_flags(monkeypatch)
        score = _make_score(
            False,
            confidence=0.3,
            estimated_savings_usd=0.005,
            reasons=["intent 'debug' not in allow-list"],
        )
        classifier_mock = _make_classifier_mock(score, "debug")

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            with patch.object(do, "_emit_delegation_event") as mock_emit:
                result = do.orchestrate_delegation(
                    prompt="fix the bug",
                    session_id="s-not-deleg",
                    correlation_id=str(uuid4()),
                )

        assert result["delegated"] is False

        mock_emit.assert_called_once()
        call_kwargs = mock_emit.call_args.kwargs
        assert call_kwargs.get("delegation_success") is False
        assert call_kwargs.get("quality_gate_passed") is False
        assert "not in allow-list" in call_kwargs.get("quality_gate_reason", "")
        assert call_kwargs.get("task_type") == "unknown"
        assert call_kwargs.get("savings_usd") == pytest.approx(0.005)

    def test_classification_exception_returns_false(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        self._enable_flags(monkeypatch)
        with patch.object(
            do, "TaskClassifier", side_effect=RuntimeError("classify broke")
        ):
            result = do.orchestrate_delegation(
                prompt="document this", correlation_id="corr-11"
            )
        assert result["delegated"] is False
        assert "classification_error" in result.get("reason", "")

    def test_classification_exception_emits_delegation_event(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Classification exception path emits a delegation event with delegation_success=False."""
        from uuid import uuid4

        self._enable_flags(monkeypatch)

        with patch.object(
            do, "TaskClassifier", side_effect=RuntimeError("classify broke")
        ):
            with patch.object(do, "_emit_delegation_event") as mock_emit:
                result = do.orchestrate_delegation(
                    prompt="document this",
                    session_id="s-cls-exc",
                    correlation_id=str(uuid4()),
                )

        assert result["delegated"] is False
        assert "classification_error" in result.get("reason", "")

        mock_emit.assert_called_once()
        call_kwargs = mock_emit.call_args.kwargs
        assert call_kwargs.get("delegation_success") is False
        assert call_kwargs.get("quality_gate_passed") is False
        assert "classification_error" in call_kwargs.get("quality_gate_reason", "")
        assert "RuntimeError" in call_kwargs.get("quality_gate_reason", "")
        assert call_kwargs.get("task_type") == "unknown"
        assert call_kwargs.get("savings_usd") == 0.0


# ---------------------------------------------------------------------------
# Endpoint resolution tests
# ---------------------------------------------------------------------------


@pytest.mark.unit
class TestEndpointResolution:
    """orchestrate_delegation fails gracefully when no endpoint is configured."""

    def _enable_flags(self, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.setenv("ENABLE_LOCAL_INFERENCE_PIPELINE", "true")
        monkeypatch.setenv("ENABLE_LOCAL_DELEGATION", "true")
        monkeypatch.setenv("LLM_CODER_URL", "http://localhost:8000")

    def test_no_endpoint_returns_false(self, monkeypatch: pytest.MonkeyPatch) -> None:
        self._enable_flags(monkeypatch)
        score = _make_score(True)
        classifier_mock = _make_classifier_mock(score, "document")

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            with patch.object(do, "_select_handler_endpoint", return_value=None):
                result = do.orchestrate_delegation(
                    prompt="document this", correlation_id="corr-20"
                )

        assert result["delegated"] is False
        assert result.get("reason") == "pre_gate:no_endpoint_configured"

    def test_no_endpoint_configured_emits_delegation_event(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Delegation event is emitted with delegation_success=False when no endpoint is configured."""
        from uuid import uuid4

        self._enable_flags(monkeypatch)
        score = _make_score(True, confidence=0.91)
        classifier_instance = MagicMock()
        classifier_instance.is_delegatable.return_value = score
        intent = MagicMock()
        intent.value = "document"
        ctx = MagicMock()
        ctx.primary_intent = intent
        classifier_instance.classify.return_value = ctx

        with patch.object(do, "TaskClassifier", return_value=classifier_instance):
            with patch.object(do, "_select_handler_endpoint", return_value=None):
                with patch.object(do, "_emit_delegation_event") as mock_emit:
                    result = do.orchestrate_delegation(
                        prompt="generate docs",
                        session_id="s1",
                        correlation_id=str(uuid4()),
                    )

        assert result["delegated"] is False
        assert "pre_gate:no_endpoint_configured" in result.get("reason", "")

        mock_emit.assert_called_once()
        call_kwargs = mock_emit.call_args.kwargs
        assert call_kwargs.get("delegation_success") is False
        assert (
            call_kwargs.get("quality_gate_reason") == "pre_gate:no_endpoint_configured"
        )

    def test_delegation_proceeds_when_endpoint_available(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        self._enable_flags(monkeypatch)
        score = _make_score(True)
        classifier_mock = _make_classifier_mock(score, "document")

        endpoint_tuple = (
            "http://localhost:8100",
            "Qwen2.5-72B",
            "You are a doc expert.",
            "doc_gen",
        )

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            with patch.object(
                do, "_select_handler_endpoint", return_value=endpoint_tuple
            ):
                with patch.object(
                    do, "_call_llm_with_system_prompt", return_value=None
                ):
                    result = do.orchestrate_delegation(
                        prompt="document func", correlation_id="corr-21"
                    )

        # Should reach the LLM call gate, not endpoint gate
        assert result.get("reason") == "pre_gate:llm_call_failed"


# ---------------------------------------------------------------------------
# LLM call failure tests
# ---------------------------------------------------------------------------


@pytest.mark.unit
class TestLlmCallFailure:
    """orchestrate_delegation handles LLM call failures gracefully."""

    def _setup(
        self, monkeypatch: pytest.MonkeyPatch, intent: str = "document"
    ) -> tuple[Any, Any, tuple[str, str, str, str]]:
        monkeypatch.setenv("ENABLE_LOCAL_INFERENCE_PIPELINE", "true")
        monkeypatch.setenv("ENABLE_LOCAL_DELEGATION", "true")
        monkeypatch.setenv("LLM_CODER_URL", "http://localhost:8000")
        score = _make_score(True, confidence=0.95)
        classifier_mock = _make_classifier_mock(score, intent)
        endpoint_tuple: tuple[str, str, str, str] = (
            "http://localhost:8100",
            "Qwen2.5-72B",
            "system prompt",
            "doc_gen",
        )
        return score, classifier_mock, endpoint_tuple

    def test_llm_call_failure_returns_delegated_false(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        _score, classifier_mock, endpoint_tuple = self._setup(monkeypatch)

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            with patch.object(
                do, "_select_handler_endpoint", return_value=endpoint_tuple
            ):
                with patch.object(
                    do, "_call_llm_with_system_prompt", return_value=None
                ):
                    result = do.orchestrate_delegation(
                        prompt="document this function", correlation_id="corr-30"
                    )

        assert result["delegated"] is False
        assert result.get("reason") == "pre_gate:llm_call_failed"

    def test_llm_call_failure_emits_delegation_event(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Delegation event must be emitted even when LLM call fails."""
        _score, classifier_mock, endpoint_tuple = self._setup(monkeypatch)

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            with patch.object(
                do, "_select_handler_endpoint", return_value=endpoint_tuple
            ):
                with patch.object(
                    do, "_call_llm_with_system_prompt", return_value=None
                ):
                    with patch.object(do, "_emit_delegation_event") as mock_emit:
                        do.orchestrate_delegation(
                            prompt="document this function", correlation_id="corr-31"
                        )

        mock_emit.assert_called_once()
        call_kwargs = mock_emit.call_args.kwargs
        assert call_kwargs.get("delegation_success") is False
        assert call_kwargs.get("quality_gate_passed") is False

    def test_empty_response_returns_delegated_false(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Empty response string from LLM -> delegated=False."""
        _score, classifier_mock, endpoint_tuple = self._setup(monkeypatch)

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            with patch.object(
                do, "_select_handler_endpoint", return_value=endpoint_tuple
            ):
                with patch.object(
                    do,
                    "_call_llm_with_system_prompt",
                    return_value=("   ", "Qwen2.5-72B"),
                ):
                    result = do.orchestrate_delegation(
                        prompt="document this function", correlation_id="corr-32"
                    )

        assert result["delegated"] is False
        assert result.get("reason") == "pre_gate:empty_response"

    def test_intent_extraction_error_emits_delegation_event(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """classify() raising -> delegated=False with intent_extraction_error and event emitted."""
        from uuid import uuid4

        monkeypatch.setenv("ENABLE_LOCAL_INFERENCE_PIPELINE", "true")
        monkeypatch.setenv("ENABLE_LOCAL_DELEGATION", "true")
        monkeypatch.setenv("LLM_CODER_URL", "http://localhost:8000")

        score = _make_score(True, confidence=0.92)
        classifier_instance = MagicMock()
        classifier_instance.is_delegatable.return_value = score
        classifier_instance.classify.side_effect = RuntimeError("classification failed")

        with patch.object(do, "TaskClassifier", return_value=classifier_instance):
            with patch.object(do, "_emit_delegation_event") as mock_emit:
                result = do.orchestrate_delegation(
                    prompt="write tests",
                    session_id="s1",
                    correlation_id=str(uuid4()),
                )

        assert result["delegated"] is False
        assert "pre_gate:intent_extraction_error" in result.get("reason", "")

        mock_emit.assert_called_once()
        call_kwargs = mock_emit.call_args.kwargs
        assert call_kwargs.get("delegation_success") is False

    def test_redaction_failure_aborts_delegation(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """When _redact_secrets raises, delegation is aborted with reason='redaction_error'.

        The old behavior forwarded the unredacted prompt to the LLM; the new
        behavior aborts immediately to prevent secret exposure (CLAUDE.md invariant:
        'Automatic secret redaction').
        """
        _score, classifier_mock, endpoint_tuple = self._setup(monkeypatch)

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            with patch.object(
                do, "_select_handler_endpoint", return_value=endpoint_tuple
            ):
                with patch.object(
                    do, "_redact_secrets", side_effect=RuntimeError("redaction broke")
                ):
                    result = do.orchestrate_delegation(
                        prompt="document this function sk-abc123secretkey",
                        correlation_id="corr-redact-fail",
                    )

        assert result["delegated"] is False
        assert result.get("reason") == "redaction_error"

    def test_redaction_failure_emits_delegation_event(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """When _redact_secrets raises, a delegation event is emitted before returning.

        All other post-classification failure paths emit a delegation event.
        The redaction_error path must not be the exception.
        """
        from uuid import uuid4

        _score, classifier_mock, endpoint_tuple = self._setup(monkeypatch)

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            with patch.object(
                do, "_select_handler_endpoint", return_value=endpoint_tuple
            ):
                with patch.object(
                    do, "_redact_secrets", side_effect=RuntimeError("redaction broke")
                ):
                    with patch.object(do, "_emit_delegation_event") as mock_emit:
                        result = do.orchestrate_delegation(
                            prompt="document this function sk-abc123secretkey",
                            session_id="s-redact",
                            correlation_id=str(uuid4()),
                        )

        assert result["delegated"] is False
        assert result.get("reason") == "redaction_error"

        mock_emit.assert_called_once()
        call_kwargs = mock_emit.call_args.kwargs
        assert call_kwargs.get("delegation_success") is False
        assert call_kwargs.get("quality_gate_passed") is False
        assert call_kwargs.get("quality_gate_reason") == "pre_gate:redaction_error"

    def test_model_name_forwarded_to_llm_call(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """The model_name from the endpoint registry is passed to _call_llm_with_system_prompt.

        Verifies that the call site passes model_name so the LLM request payload
        uses the actual configured model identifier instead of the hardcoded 'local'.
        """
        _score, classifier_mock, endpoint_tuple = self._setup(monkeypatch)
        # endpoint_tuple[1] is the model_name from the registry ("Qwen2.5-72B")

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            with patch.object(
                do, "_select_handler_endpoint", return_value=endpoint_tuple
            ):
                with patch.object(
                    do, "_call_llm_with_system_prompt", return_value=None
                ) as mock_llm_call:
                    do.orchestrate_delegation(
                        prompt="document this function",
                        correlation_id="corr-model-name",
                    )

        mock_llm_call.assert_called_once()
        call_args = mock_llm_call.call_args
        # Positional args: (prompt, endpoint_url, system_prompt, model_name)
        assert call_args.args[3] == "Qwen2.5-72B"


# ---------------------------------------------------------------------------
# Quality gate failure tests
# ---------------------------------------------------------------------------


@pytest.mark.unit
class TestQualityGateFailure:
    """orchestrate_delegation falls back to Claude when quality gate fails."""

    def _setup(
        self, monkeypatch: pytest.MonkeyPatch, intent: str = "document"
    ) -> tuple[Any, Any, tuple[str, str, str, str]]:
        monkeypatch.setenv("ENABLE_LOCAL_INFERENCE_PIPELINE", "true")
        monkeypatch.setenv("ENABLE_LOCAL_DELEGATION", "true")
        monkeypatch.setenv("LLM_CODER_URL", "http://localhost:8000")
        score = _make_score(True, confidence=0.95)
        classifier_mock = _make_classifier_mock(score, intent)
        endpoint_tuple: tuple[str, str, str, str] = (
            "http://localhost:8100",
            "Qwen2.5-72B",
            "system prompt",
            "doc_gen",
        )
        return score, classifier_mock, endpoint_tuple

    def test_quality_gate_failure_returns_delegated_false(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        _score, classifier_mock, endpoint_tuple = self._setup(monkeypatch)

        # LLM returns a response that will fail the doc quality gate (no markers)
        bad_response = "x" * 120  # Long enough but no docstring markers

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            with patch.object(
                do, "_select_handler_endpoint", return_value=endpoint_tuple
            ):
                with patch.object(
                    do,
                    "_call_llm_with_system_prompt",
                    return_value=(bad_response, "Qwen2.5-72B"),
                ):
                    result = do.orchestrate_delegation(
                        prompt="document this function", correlation_id="corr-40"
                    )

        assert result["delegated"] is False
        assert result.get("reason") == "quality_gate_failed"
        assert "quality_gate_reason" in result

    def test_quality_gate_failure_emits_delegation_event(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Delegation event must be emitted when quality gate fails."""
        _score, classifier_mock, endpoint_tuple = self._setup(monkeypatch)
        bad_response = "x" * 120  # No docstring markers

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            with patch.object(
                do, "_select_handler_endpoint", return_value=endpoint_tuple
            ):
                with patch.object(
                    do,
                    "_call_llm_with_system_prompt",
                    return_value=(bad_response, "Qwen2.5-72B"),
                ):
                    with patch.object(do, "_emit_delegation_event") as mock_emit:
                        do.orchestrate_delegation(
                            prompt="document this function", correlation_id="corr-41"
                        )

        mock_emit.assert_called_once()
        call_kwargs = mock_emit.call_args.kwargs
        assert call_kwargs["quality_gate_passed"] is False
        assert call_kwargs["delegation_success"] is False
        assert call_kwargs["task_type"] == "document"

    def test_quality_gate_failure_does_not_emit_compliance_advisory(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Compliance advisory must NOT be emitted when quality gate fails.

        _emit_compliance_advisory is on the success path only.  A failed
        quality gate means no handler response was accepted, so there is
        nothing to evaluate for compliance.
        """
        _score, classifier_mock, endpoint_tuple = self._setup(monkeypatch)
        bad_response = "x" * 120  # No docstring markers — fails doc quality gate

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            with patch.object(
                do, "_select_handler_endpoint", return_value=endpoint_tuple
            ):
                with patch.object(
                    do,
                    "_call_llm_with_system_prompt",
                    return_value=(bad_response, "Qwen2.5-72B"),
                ):
                    with patch.object(do, "_emit_delegation_event"):
                        with patch.object(
                            do, "_emit_compliance_advisory"
                        ) as mock_compliance:
                            do.orchestrate_delegation(
                                prompt="document this function",
                                correlation_id="corr-42",
                            )

        assert mock_compliance.call_count == 0


# ---------------------------------------------------------------------------
# Full success path tests
# ---------------------------------------------------------------------------

# A doc LLM response that passes the quality gate
_GOOD_DOC_RESPONSE = (
    "def calculate(x: int) -> int:\n"
    '    """Calculate the result.\n\n'
    "    Args:\n"
    "        x: Input integer.\n\n"
    "    Returns:\n"
    "        int: The result.\n"
    '    """\n'
    "    return x * 2"
)

# A test LLM response that passes the quality gate
_GOOD_TEST_RESPONSE = (
    "@pytest.mark.unit\n"
    "def test_calculate_returns_correct_value():\n"
    "    result = calculate(5)\n"
    "    assert result == 10\n"
)

# A research LLM response that passes the quality gate
_GOOD_RESEARCH_RESPONSE = (
    "Kafka is a distributed event streaming platform designed for high-throughput, "
    "fault-tolerant, and scalable data pipelines. It uses a publish-subscribe model "
    "where producers write to topics and consumers read from them."
)


@pytest.mark.unit
class TestOrchestratedDelegationSuccess:
    """orchestrate_delegation returns correct structure on full success."""

    def _setup_all_gates(
        self,
        monkeypatch: pytest.MonkeyPatch,
        intent: str = "document",
        llm_response: str | None = None,
        handler_name: str = "doc_gen",
        model_name: str = "Qwen2.5-72B",
        endpoint_url: str = "http://llm-embedding-host:8100",
    ) -> tuple[Any, Any, tuple[str, str, str, str], str]:
        monkeypatch.setenv("ENABLE_LOCAL_INFERENCE_PIPELINE", "true")
        monkeypatch.setenv("ENABLE_LOCAL_DELEGATION", "true")
        monkeypatch.setenv("LLM_CODER_URL", "http://localhost:8000")
        score = _make_score(
            True,
            confidence=0.97,
            estimated_savings_usd=0.0112,
            reasons=["intent 'document' is in the delegation allow-list"],
        )
        classifier_mock = _make_classifier_mock(score, intent)
        endpoint_tuple: tuple[str, str, str, str] = (
            endpoint_url,
            model_name,
            "You are a documentation expert.",
            handler_name,
        )
        if llm_response is None:
            llm_response = _GOOD_DOC_RESPONSE
        return score, classifier_mock, endpoint_tuple, llm_response

    def test_happy_path_returns_delegated_true(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """All gates pass -> delegated=True with full metadata."""
        _score, classifier_mock, endpoint_tuple, llm_response = self._setup_all_gates(
            monkeypatch
        )

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            with patch.object(
                do, "_select_handler_endpoint", return_value=endpoint_tuple
            ):
                with patch.object(
                    do,
                    "_call_llm_with_system_prompt",
                    return_value=(llm_response, "Qwen2.5-72B"),
                ):
                    with patch.object(do, "_emit_delegation_event"):
                        with patch.object(do, "_emit_compliance_advisory"):
                            result = do.orchestrate_delegation(
                                prompt="document this function",
                                correlation_id="corr-50",
                            )

        assert result["delegated"] is True
        assert "response" in result
        assert "[Local Model Response - Qwen2.5-72B]" in result["response"]
        assert result["model"] == "Qwen2.5-72B"
        assert result["confidence"] == pytest.approx(0.97)
        assert result["savings_usd"] == pytest.approx(0.0112)
        assert result["handler"] == "doc_gen"
        assert result["quality_gate_passed"] is True
        assert result["intent"] == "document"
        assert isinstance(result["latency_ms"], int)
        assert result["latency_ms"] >= 0

    def test_happy_path_emits_delegation_event(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Delegation event is emitted with delegation_success=True on success."""
        _score, classifier_mock, endpoint_tuple, llm_response = self._setup_all_gates(
            monkeypatch
        )

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            with patch.object(
                do, "_select_handler_endpoint", return_value=endpoint_tuple
            ):
                with patch.object(
                    do,
                    "_call_llm_with_system_prompt",
                    return_value=(llm_response, "Qwen2.5-72B"),
                ):
                    with patch.object(do, "_emit_delegation_event") as mock_emit:
                        with patch.object(do, "_emit_compliance_advisory"):
                            do.orchestrate_delegation(
                                prompt="document func", correlation_id="corr-51"
                            )

        mock_emit.assert_called_once()
        call_kwargs = mock_emit.call_args.kwargs
        assert call_kwargs["delegation_success"] is True
        assert call_kwargs["quality_gate_passed"] is True
        assert call_kwargs["task_type"] == "document"
        assert call_kwargs["handler_name"] == "doc_gen"

    def test_happy_path_emits_compliance_advisory(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Compliance advisory is emitted on successful delegation."""
        _score, classifier_mock, endpoint_tuple, llm_response = self._setup_all_gates(
            monkeypatch
        )

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            with patch.object(
                do, "_select_handler_endpoint", return_value=endpoint_tuple
            ):
                with patch.object(
                    do,
                    "_call_llm_with_system_prompt",
                    return_value=(llm_response, "Qwen2.5-72B"),
                ):
                    with patch.object(do, "_emit_delegation_event"):
                        with patch.object(
                            do, "_emit_compliance_advisory"
                        ) as mock_advisory:
                            do.orchestrate_delegation(
                                prompt="document func", correlation_id="corr-52"
                            )

        mock_advisory.assert_called_once()

    def test_response_contains_attribution_and_reasons(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Formatted response includes attribution header and reasons footer."""
        _score, classifier_mock, endpoint_tuple, llm_response = self._setup_all_gates(
            monkeypatch
        )

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            with patch.object(
                do, "_select_handler_endpoint", return_value=endpoint_tuple
            ):
                with patch.object(
                    do,
                    "_call_llm_with_system_prompt",
                    return_value=(llm_response, "Qwen2.5-72B"),
                ):
                    with patch.object(do, "_emit_delegation_event"):
                        with patch.object(do, "_emit_compliance_advisory"):
                            result = do.orchestrate_delegation(
                                prompt="document this function",
                                correlation_id="corr-53",
                            )

        assert "---" in result["response"]
        assert "delegation allow-list" in result["response"]

    def test_never_raises_on_unexpected_error(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """SystemError (subclass of Exception) is caught by the Gate 2 inner
        try/except (around lines 717-738 in delegation_orchestrator.py), not
        the outer guard. Result is delegated=False with reason containing
        "classification_error"."""
        monkeypatch.setenv("ENABLE_LOCAL_INFERENCE_PIPELINE", "true")
        monkeypatch.setenv("ENABLE_LOCAL_DELEGATION", "true")
        monkeypatch.setenv("LLM_CODER_URL", "http://localhost:8000")

        with patch.object(
            do, "TaskClassifier", side_effect=SystemError("unrecoverable")
        ):
            result = do.orchestrate_delegation(
                prompt="document this", correlation_id="corr-err"
            )

        assert result["delegated"] is False
        assert "classification_error" in result.get("reason", "")

    def test_orchestrator_unexpected_exception_returns_false(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Outer try/except catches AttributeError from score attribute access.

        is_delegatable() returns successfully, but accessing score.delegatable
        raises AttributeError — this access happens outside the inner
        try/except in Gate 2, so only the outer guard can catch it.
        The function must return the safe orchestrator_error fallback.
        """
        monkeypatch.setenv("ENABLE_LOCAL_INFERENCE_PIPELINE", "true")
        monkeypatch.setenv("ENABLE_LOCAL_DELEGATION", "true")
        monkeypatch.setenv("LLM_CODER_URL", "http://localhost:8000")

        # Build a score object whose .delegatable property raises AttributeError.
        # is_delegatable() returns this score successfully; the AttributeError
        # is raised when orchestrate_delegation accesses score.delegatable outside
        # the inner try/except block.
        bad_score = MagicMock(spec=[])  # spec=[] means NO attributes are allowed
        classifier_instance = MagicMock()
        classifier_instance.is_delegatable.return_value = bad_score

        with patch.object(do, "TaskClassifier", return_value=classifier_instance):
            result = do.orchestrate_delegation(
                prompt="document this function", correlation_id="corr-attr-err"
            )

        assert result["delegated"] is False
        assert result.get("reason") == "orchestrator_error"

    def test_result_always_has_delegated_key(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Every code path returns a dict with 'delegated' key."""
        for env_pip, env_del in [
            ("false", "false"),
            ("true", "false"),
            ("false", "true"),
            ("true", "true"),
        ]:
            monkeypatch.setenv("ENABLE_LOCAL_INFERENCE_PIPELINE", env_pip)
            monkeypatch.setenv("ENABLE_LOCAL_DELEGATION", env_del)
            score = _make_score(False)
            classifier_mock = _make_classifier_mock(score, "debug")
            with patch.object(do, "TaskClassifier", return_value=classifier_mock):
                result = do.orchestrate_delegation(
                    prompt="some prompt", correlation_id="corr-always"
                )
            assert "delegated" in result
            assert isinstance(result["delegated"], bool)

    def test_test_intent_routes_to_test_boilerplate(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """TEST intent uses test_boilerplate handler and passes quality gate."""
        _score, classifier_mock, endpoint_tuple, _llm = self._setup_all_gates(
            monkeypatch,
            intent="test",
            llm_response=_GOOD_TEST_RESPONSE,
            handler_name="test_boilerplate",
            model_name="Qwen3-Coder-30B",
            endpoint_url="http://llm-coder-host:8000",
        )
        # Override the reasons to reflect test intent
        classifier_mock.is_delegatable.return_value.reasons = [
            "intent 'test' is in the delegation allow-list"
        ]

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            with patch.object(
                do, "_select_handler_endpoint", return_value=endpoint_tuple
            ):
                with patch.object(
                    do,
                    "_call_llm_with_system_prompt",
                    return_value=(_GOOD_TEST_RESPONSE, "Qwen3-Coder-30B"),
                ):
                    with patch.object(do, "_emit_delegation_event"):
                        with patch.object(do, "_emit_compliance_advisory"):
                            result = do.orchestrate_delegation(
                                prompt="write tests for calculate",
                                correlation_id="corr-test",
                            )

        assert result["delegated"] is True
        assert result["handler"] == "test_boilerplate"
        assert result["intent"] == "test"

    def test_research_intent_routes_to_code_review(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """RESEARCH intent uses code_review handler and passes quality gate."""
        _score, classifier_mock, endpoint_tuple, _llm = self._setup_all_gates(
            monkeypatch,
            intent="research",
            llm_response=_GOOD_RESEARCH_RESPONSE,
            handler_name="code_review",
            model_name="Qwen3-Coder-30B",
            endpoint_url="http://llm-coder-host:8000",
        )
        classifier_mock.is_delegatable.return_value.reasons = [
            "intent 'research' is in the delegation allow-list"
        ]

        with patch.object(do, "TaskClassifier", return_value=classifier_mock):
            with patch.object(
                do, "_select_handler_endpoint", return_value=endpoint_tuple
            ):
                with patch.object(
                    do,
                    "_call_llm_with_system_prompt",
                    return_value=(_GOOD_RESEARCH_RESPONSE, "Qwen3-Coder-30B"),
                ):
                    with patch.object(do, "_emit_delegation_event"):
                        with patch.object(do, "_emit_compliance_advisory"):
                            result = do.orchestrate_delegation(
                                prompt="explain how kafka works",
                                correlation_id="corr-research",
                            )

        assert result["delegated"] is True
        assert result["handler"] == "code_review"
        assert result["intent"] == "research"


# ---------------------------------------------------------------------------
# _emit_delegation_event unit tests (real function, mock only emit_event)
# ---------------------------------------------------------------------------


@pytest.mark.unit
class TestEmitDelegationEvent:
    """Tests for the internal logic of _emit_delegation_event.

    Calls the REAL function — only the inner ``emit_event`` call is mocked
    so that we can inspect the payload that would be sent to Kafka.

    Because ``emit_event`` is imported with a local ``from emit_client_wrapper
    import emit_event`` inside the function's try block, we inject the mock
    via ``sys.modules`` before the call so the local import resolves to our
    mock object.
    """

    def _run(
        self,
        mock_emit: MagicMock,
        *,
        session_id: str = "abc-session",
        correlation_id: str = "00000000-0000-0000-0000-000000000001",
        task_type: str = "document",
        handler_name: str = "doc_gen",
        model_name: str = "Qwen2.5-72B",
        quality_gate_passed: bool = True,
        quality_gate_reason: str = "",
        delegation_success: bool = True,
        savings_usd: float = 0.01,
        latency_ms: int = 250,
        emitted_at: datetime | None = None,
    ) -> None:
        """Helper: inject mock emit_client_wrapper and call _emit_delegation_event."""
        if emitted_at is None:
            emitted_at = datetime(2025, 6, 1, 12, 0, 0, tzinfo=UTC)

        # Build a fake emit_client_wrapper module with our mock emit_event.
        fake_module = MagicMock()
        fake_module.emit_event = mock_emit

        original = sys.modules.get("emit_client_wrapper")
        sys.modules["emit_client_wrapper"] = fake_module
        try:
            do._emit_delegation_event(
                session_id=session_id,
                correlation_id=correlation_id,
                task_type=task_type,
                handler_name=handler_name,
                model_name=model_name,
                quality_gate_passed=quality_gate_passed,
                quality_gate_reason=quality_gate_reason,
                delegation_success=delegation_success,
                savings_usd=savings_usd,
                latency_ms=latency_ms,
                emitted_at=emitted_at,
            )
        finally:
            if original is None:
                sys.modules.pop("emit_client_wrapper", None)
            else:
                sys.modules["emit_client_wrapper"] = original

    def test_emit_uses_unknown_session_id_when_empty(self) -> None:
        """session_id='' causes the payload to use 'unknown' for session_id."""
        mock_emit = MagicMock()
        self._run(mock_emit, session_id="")

        mock_emit.assert_called_once()
        payload = mock_emit.call_args.kwargs["payload"]
        assert payload["session_id"] == "unknown"

    def test_emit_truncates_quality_gate_reason_at_200(self) -> None:
        """A quality_gate_reason longer than 200 chars is truncated to 200 in the payload."""
        long_reason = "x" * 300
        mock_emit = MagicMock()
        self._run(
            mock_emit,
            quality_gate_passed=False,
            quality_gate_reason=long_reason,
            delegation_success=False,
        )

        mock_emit.assert_called_once()
        payload = mock_emit.call_args.kwargs["payload"]
        assert payload["quality_gate_reason"] is not None
        assert len(payload["quality_gate_reason"]) == 200
        assert payload["quality_gate_reason"] == "x" * 200

    def test_emit_generates_uuid_when_correlation_id_invalid(self) -> None:
        """Passing a non-UUID correlation_id does not raise; a placeholder UUID is generated."""
        mock_emit = MagicMock()
        # Should not raise even with an invalid UUID string.
        self._run(mock_emit, correlation_id="not-a-uuid")

        # emit_event was still called — function recovered gracefully.
        mock_emit.assert_called_once()

    def test_emit_swallows_exceptions(self) -> None:
        """If emit_event raises, _emit_delegation_event does not propagate the exception."""
        mock_emit = MagicMock(side_effect=RuntimeError("Kafka unavailable"))
        # Must not raise.
        self._run(mock_emit)


# ---------------------------------------------------------------------------
# orchestrate_delegation alias test
# ---------------------------------------------------------------------------


@pytest.mark.unit
def test_orchestrate_delegation_returns_feature_disabled(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """orchestrate_delegation returns feature_disabled when no LLM endpoints are configured."""
    monkeypatch.delenv("ENABLE_LOCAL_INFERENCE_PIPELINE", raising=False)
    monkeypatch.delenv("ENABLE_LOCAL_DELEGATION", raising=False)
    monkeypatch.delenv("LLM_CODER_URL", raising=False)
    monkeypatch.delenv("LLM_DEEPSEEK_R1_URL", raising=False)
    result = do.orchestrate_delegation(
        prompt="test prompt", correlation_id="corr-alias"
    )
    assert result["delegated"] is False
    assert result.get("reason") == "feature_disabled"


# ---------------------------------------------------------------------------
# Classifier instance caching tests
# ---------------------------------------------------------------------------


@pytest.mark.unit
class TestClassifierInstanceCaching:
    """_get_classifier() caches the TaskClassifier instance across calls.

    Verifies that repeated calls to orchestrate_delegation reuse a single
    TaskClassifier instance instead of constructing a new one each time.
    Also verifies that patching TaskClassifier (as tests do) invalidates the
    cache so the patched class gets a fresh instance.
    """

    def setup_method(self) -> None:
        """Reset the cache before each test to avoid cross-test contamination."""
        do._reset_classifier_cache()

    def teardown_method(self) -> None:
        """Reset the cache after each test so subsequent tests start clean."""
        do._reset_classifier_cache()

    def test_classifier_instantiated_only_once_across_two_calls(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """TaskClassifier.__init__ is called only once when orchestrate_delegation
        is invoked twice in succession (cache hit on second call).
        """
        monkeypatch.setenv("ENABLE_LOCAL_INFERENCE_PIPELINE", "true")
        monkeypatch.setenv("ENABLE_LOCAL_DELEGATION", "true")
        monkeypatch.setenv("LLM_CODER_URL", "http://localhost:8000")

        score = _make_score(False, reasons=["not delegatable"])
        classifier_instance = _make_classifier_mock(score, "debug")

        init_call_count = 0

        class TrackingClassifier:
            def __init__(self) -> None:
                nonlocal init_call_count
                init_call_count += 1
                # Delegate all attribute access to the pre-built mock.
                self._inner = classifier_instance

            def is_delegatable(self, prompt: str) -> Any:
                return self._inner.is_delegatable(prompt)

            def classify(self, prompt: str) -> Any:
                return self._inner.classify(prompt)

        with patch.object(do, "TaskClassifier", TrackingClassifier):
            do.orchestrate_delegation(prompt="fix the bug", correlation_id="c-1")
            do.orchestrate_delegation(prompt="fix another bug", correlation_id="c-2")

        assert init_call_count == 1, (
            f"TaskClassifier was instantiated {init_call_count} times; "
            "expected exactly 1 (cache hit on second call)"
        )

    def test_cache_miss_when_classifier_class_is_replaced(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Patching TaskClassifier to a different class causes _get_classifier()
        to construct a new instance (cache type-check fails).
        """
        monkeypatch.setenv("ENABLE_LOCAL_INFERENCE_PIPELINE", "true")
        monkeypatch.setenv("ENABLE_LOCAL_DELEGATION", "true")
        monkeypatch.setenv("LLM_CODER_URL", "http://localhost:8000")

        score = _make_score(False, reasons=["not delegatable"])
        classifier_instance = _make_classifier_mock(score, "debug")

        first_init_count = 0
        second_init_count = 0

        class FirstClassifier:
            def __init__(self) -> None:
                nonlocal first_init_count
                first_init_count += 1
                self._inner = classifier_instance

            def is_delegatable(self, prompt: str) -> Any:
                return self._inner.is_delegatable(prompt)

            def classify(self, prompt: str) -> Any:
                return self._inner.classify(prompt)

        class SecondClassifier:
            def __init__(self) -> None:
                nonlocal second_init_count
                second_init_count += 1
                self._inner = classifier_instance

            def is_delegatable(self, prompt: str) -> Any:
                return self._inner.is_delegatable(prompt)

            def classify(self, prompt: str) -> Any:
                return self._inner.classify(prompt)

        # First call — installs FirstClassifier in the cache.
        with patch.object(do, "TaskClassifier", FirstClassifier):
            do.orchestrate_delegation(prompt="document this", correlation_id="c-3")

        # Second call — TaskClassifier is now SecondClassifier; cache should miss.
        with patch.object(do, "TaskClassifier", SecondClassifier):
            do.orchestrate_delegation(prompt="document that", correlation_id="c-4")

        assert first_init_count == 1, "FirstClassifier should be instantiated once"
        assert second_init_count == 1, (
            "SecondClassifier should be instantiated once (cache miss due to class change)"
        )

    def test_reset_classifier_cache_clears_cached_instance(self) -> None:
        """_reset_classifier_cache() sets _cached_classifier back to None."""
        # Inject a fake instance directly into the module cache.
        do._cached_classifier = MagicMock()
        assert do._cached_classifier is not None

        do._reset_classifier_cache()

        assert do._cached_classifier is None
