"""Real connector dispatch must run policies against each composed name."""

import json

import pytest


@pytest.mark.parametrize("blocked_by", ["hook", "execution"])
def test_remote_entries_run_request_hook_and_execution_policies(monkeypatch, blocked_by):
    import model_tools
    import hermes_cli.plugins as plugins
    from tools.registry import invalidate_check_fn_cache
    from tools.connectors.gateway import bridge, config

    monkeypatch.setattr(config, "connectors_available", lambda: True)
    monkeypatch.setattr(bridge, "connectors_available", lambda: True)
    invalidate_check_fn_cache()
    denied = "connectors__gmail__SEND_EMAIL"
    rewritten = "connectors__slack__POST_MESSAGE"
    calls = [{"name": name, "arguments": {"body": "original"}} for name in (denied, rewritten)]
    events = []
    wire = []

    def request(**kw):
        events.append(("request", kw["tool_name"]))
        assert kw["args"] == {"body": "original"}
        assert kw["session_id"] == "policy-session"
        return {"args": {"body": "request-rewrite"}, "source": "test-policy"}

    def hook(name, args, **kw):
        events.append(("hook", name))
        assert args == {"body": "request-rewrite"}
        assert kw["middleware_trace"] == [{"source": "test-policy"}]
        assert kw["tool_call_id"] == "policy-call"
        if name == denied and blocked_by == "hook":
            return "hook denied", None
        return None, {"body": "hook-rewrite"}

    def execution(**kw):
        events.append(("execution", kw["tool_name"]))
        assert kw["args"] == {"body": "hook-rewrite"}
        assert kw["original_args"] == {"body": "original"}
        assert kw["session_id"] == "policy-session"
        if kw["tool_name"] == denied:
            return json.dumps({"error": {"code": "POLICY_DENIED", "message": "execution denied", "policy": "no-mail"}})
        return kw["next_call"]({})  # Empty dict must reach the wire, not original arguments.

    monkeypatch.setattr(plugins.get_plugin_manager(), "_middleware", {
        "tool_request": [request], "tool_execution": [execution]})
    monkeypatch.setattr(plugins, "_dispatch_pre_tool_call_hooks", hook)

    class Client:
        def execute(self, planned):
            wire.extend(planned)
            return [{"data": "remote-ok", "error": None} for _ in planned]

    monkeypatch.setattr(bridge, "_default_client_factory", Client)
    kwargs = dict(enabled_toolsets=["connections"], session_id="policy-session", tool_call_id="policy-call",
                  skip_pre_tool_call_hook=True, skip_tool_request_middleware=True,
                  skip_tool_execution_middleware=True)
    result = json.loads(model_tools.handle_function_call("tool_call", {"calls": calls}, **kwargs))
    assert "denied" in json.dumps(result["results"][0]["error"])
    if blocked_by == "execution":
        assert result["results"][0]["error"] == {
            "code": "POLICY_DENIED", "message": "execution denied", "policy": "no-mail"}
    assert result["results"][1]["response"] == "remote-ok"
    assert [(p.name, p.arguments) for p in wire] == [(rewritten, {})]
    expected_denied = [("request", denied), ("hook", denied)]
    if blocked_by == "execution":
        expected_denied.append(("execution", denied))
    assert events == expected_denied + [(phase, rewritten) for phase in ("request", "hook", "execution")]
    assert result["total_count"] == 2 and result["success_count"] == result["error_count"] == 1

    wire.clear()
    result = json.loads(model_tools.handle_function_call("tool_call", {"calls": calls[:1]}, **kwargs))
    assert result["error_count"] == 1
    assert not wire  # An entirely blocked batch never constructs/sends an execute request.


def test_stop_during_a_connector_batch_leaves_unstarted_entries_unsent(monkeypatch):
    import model_tools
    from tools.interrupt import set_interrupt
    from tools.registry import invalidate_check_fn_cache
    from tools.connectors.gateway import bridge, config

    monkeypatch.setattr(config, "connectors_available", lambda: True)
    monkeypatch.setattr(bridge, "connectors_available", lambda: True)
    invalidate_check_fn_cache()
    wire = []

    class Client:
        def execute(self, planned):
            wire.extend(planned)
            set_interrupt(True)  # /stop lands while the first entry is on the wire.
            return [{"data": "remote-ok", "error": None} for _ in planned]

    monkeypatch.setattr(bridge, "_default_client_factory", Client)
    calls = [{"name": f"connectors__gmail__{tool}", "arguments": {}}
             for tool in ("FETCH_EMAILS", "SEND_EMAIL", "CREATE_DRAFT")]
    try:
        result = json.loads(model_tools.handle_function_call(
            "tool_call", {"calls": calls}, enabled_toolsets=["connections"], session_id="stop-session",
            skip_pre_tool_call_hook=True, skip_tool_request_middleware=True,
            skip_tool_execution_middleware=True))
    finally:
        set_interrupt(False)
    assert [p.name for p in wire] == [calls[0]["name"]]
    assert result["results"][0]["response"] == "remote-ok"
    assert [(e["index"], e["name"], e["error"]["code"]) for e in result["results"][1:]] == [
        (1, calls[1]["name"], "INTERRUPTED"), (2, calls[2]["name"], "INTERRUPTED")]
    assert result["total_count"] == 3 and result["success_count"] == 1 and result["error_count"] == 2


def test_disabled_connections_cannot_be_called_through_a_stale_schema(monkeypatch):
    from tools.connectors import managed
    from tools.connectors.gateway import config
    from tools.registry import registry

    monkeypatch.setattr(config, "connectors_available", lambda: False)
    monkeypatch.setattr(managed, "managed_client",
                        lambda: (_ for _ in ()).throw(AssertionError("disabled connector attempted I/O")))
    result = json.loads(registry.dispatch("manage_connections", {"action": "connect", "connectors": ["gmail"]}))
    assert "not available" in result["error"]
