"""Regression tests for OpenCode-Go model-picker runtime routing (#3895)."""

import queue
import sys
import threading
import types
from unittest import mock

import api.config
import api.oauth
import api.streaming as streaming


def test_runtime_preferred_base_url_uses_runtime_value_for_pooled_provider():
    assert streaming._runtime_preferred_base_url(
        {"provider": "opencode-go", "base_url": "https://opencode.example.com/api"},
        "opencode-go",
        "https://opencode.example.com/api/v1",
    ) == "https://opencode.example.com/api"


def test_runtime_preferred_base_url_keeps_custom_config_base_url():
    assert streaming._runtime_preferred_base_url(
        {"provider": "custom:opencode-proxy", "base_url": "https://runtime.example.com"},
        "custom:opencode-proxy",
        "https://config.example.com/v1",
    ) == "https://config.example.com/v1"


def test_runtime_preferred_base_url_uses_runtime_for_custom_provider_without_config():
    assert streaming._runtime_preferred_base_url(
        {"provider": "custom:opencode-proxy", "base_url": "https://runtime.example.com"},
        "custom:opencode-proxy",
        None,
    ) == "https://runtime.example.com"


def test_runtime_preferred_base_url_preserves_different_endpoint_config_override():
    """Codex CORE regression guard: an explicit providers.<id>.base_url override
    at a DIFFERENT host/port must NOT be clobbered by the runtime default. Only a
    same-endpoint runtime URL (the #3895 /v1-dedup case) should win."""
    # LM Studio pinned at a LAN IP — must be preserved over the runtime localhost default.
    assert streaming._runtime_preferred_base_url(
        {"provider": "lmstudio", "base_url": "http://localhost:1234/v1"},
        "lmstudio",
        "http://10.0.0.5:1234/v1",
    ) == "http://10.0.0.5:1234/v1"
    # OpenRouter mirror override preserved over the runtime canonical host.
    assert streaming._runtime_preferred_base_url(
        {"provider": "openrouter", "base_url": "https://openrouter.ai/api/v1"},
        "openrouter",
        "https://my-mirror.example.com/api/v1",
    ) == "https://my-mirror.example.com/api/v1"


def test_runtime_preferred_base_url_prefers_runtime_for_same_endpoint_path_normalization():
    """The #3895 case: same scheme+host+port, runtime just strips a duplicated
    path segment — runtime (corrected) form wins."""
    assert streaming._runtime_preferred_base_url(
        {"provider": "opencode-go", "base_url": "https://opencode.example.com/zen/go"},
        "opencode-go",
        "https://opencode.example.com/zen/go/v1",
    ) == "https://opencode.example.com/zen/go"
    # Same host different port = different endpoint → configured wins.
    assert streaming._runtime_preferred_base_url(
        {"provider": "someprov", "base_url": "https://host.example.com:8080/v1"},
        "someprov",
        "https://host.example.com:9090/v1",
    ) == "https://host.example.com:9090/v1"


def test_streaming_passes_target_model_and_prefers_runtime_base_url(monkeypatch):
    captured = {}

    class FakeSession:
        def __init__(self):
            self.session_id = "sess-3895"
            self.title = "OpenCode test"
            self.workspace = "/tmp"
            self.model = "glm-5.1"
            self.messages = []
            self.personality = None
            self.input_tokens = 0
            self.output_tokens = 0
            self.estimated_cost = None
            self.tool_calls = []
            self.active_stream_id = None
            self.pending_user_message = None
            self.pending_attachments = []
            self.pending_started_at = None

        def save(self, touch_updated_at=True):
            self._saved = touch_updated_at

        def compact(self):
            return {
                "session_id": self.session_id,
                "title": self.title,
                "workspace": self.workspace,
                "model": self.model,
                "created_at": 0,
                "updated_at": 0,
                "pinned": False,
                "archived": False,
                "project_id": None,
                "profile": None,
                "input_tokens": self.input_tokens,
                "output_tokens": self.output_tokens,
                "estimated_cost": self.estimated_cost,
                "personality": self.personality,
            }

    class CapturingAgent:
        def __init__(
            self,
            model=None,
            provider=None,
            base_url=None,
            api_key=None,
            platform=None,
            quiet_mode=False,
            enabled_toolsets=None,
            fallback_model=None,
            session_id=None,
            session_db=None,
            stream_delta_callback=None,
            reasoning_callback=None,
            tool_progress_callback=None,
            clarify_callback=None,
            **kwargs,
        ):
            captured["init_kwargs"] = {
                "model": model,
                "provider": provider,
                "base_url": base_url,
                "api_key": api_key,
                "session_id": session_id,
                "session_db": session_db,
            }
            self.session_id = session_id
            self.context_compressor = None
            self.session_prompt_tokens = 0
            self.session_completion_tokens = 0
            self.session_estimated_cost_usd = None
            self.reasoning_config = None
            self.ephemeral_system_prompt = None
            self._last_error = None

        def run_conversation(self, **kwargs):
            captured["run_kwargs"] = kwargs
            return {
                "messages": [
                    {"role": "user", "content": kwargs["persist_user_message"]},
                    {"role": "assistant", "content": "ok"},
                ]
            }

        def interrupt(self, _message):
            captured["interrupted"] = _message

    fake_session = FakeSession()
    fake_stream_id = "stream-3895"
    fake_session.active_stream_id = fake_stream_id
    fake_queue = queue.Queue()
    fake_runtime_module = types.ModuleType("hermes_cli.runtime_provider")
    resolve_runtime_provider = mock.Mock(
        return_value={
            "provider": "opencode-go",
            "base_url": "https://opencode.example.com/api",
            "api_key": "rt-key",
        }
    )
    fake_runtime_module.resolve_runtime_provider = resolve_runtime_provider
    fake_hermes_cli = types.ModuleType("hermes_cli")
    fake_hermes_cli.runtime_provider = fake_runtime_module
    fake_hermes_state = types.ModuleType("hermes_state")
    fake_hermes_state.SessionDB = mock.Mock(return_value=object())

    def fake_runtime_lock(resolver, **kwargs):
        return resolver(**kwargs)

    monkeypatch.setattr(
        api.oauth,
        "resolve_runtime_provider_with_anthropic_env_lock",
        fake_runtime_lock,
    )
    monkeypatch.setattr(streaming, "get_session", lambda _session_id: fake_session)
    monkeypatch.setattr(streaming, "_get_ai_agent", lambda: CapturingAgent)
    monkeypatch.setattr(
        streaming,
        "resolve_model_provider",
        lambda *_args, **_kwargs: (
            "glm-5.1",
            "opencode-go",
            "https://opencode.example.com/api/v1",
        ),
    )
    monkeypatch.setattr("api.config.get_config", lambda: {})
    monkeypatch.setattr("api.config._resolve_cli_toolsets", lambda *_args, **_kwargs: [])
    monkeypatch.setitem(sys.modules, "hermes_cli", fake_hermes_cli)
    monkeypatch.setitem(sys.modules, "hermes_cli.runtime_provider", fake_runtime_module)
    monkeypatch.setitem(sys.modules, "hermes_state", fake_hermes_state)

    try:
        streaming.STREAMS[fake_stream_id] = fake_queue
        streaming._run_agent_streaming(
            session_id=fake_session.session_id,
            msg_text="hello from picker",
            model="glm-5.1",
            workspace="/tmp",
            stream_id=fake_stream_id,
        )
    finally:
        streaming.STREAMS.pop(fake_stream_id, None)
        streaming.AGENT_INSTANCES.pop(fake_stream_id, None)

    resolve_runtime_provider.assert_called_once_with(
        requested="opencode-go",
        target_model="glm-5.1",
    )
    assert captured["init_kwargs"]["provider"] == "opencode-go"
    assert captured["init_kwargs"]["base_url"] == "https://opencode.example.com/api"
    assert captured["init_kwargs"]["api_key"] == "rt-key"


def test_runtime_provider_lock_wrapper_forwards_target_model():
    calls = {}

    def fake_resolver(**kwargs):
        calls["kwargs"] = kwargs
        return {"provider": kwargs.get("requested"), "base_url": None, "api_key": None}

    result = api.oauth.resolve_runtime_provider_with_anthropic_env_lock(
        fake_resolver,
        requested="opencode-go",
        target_model="glm-5.1",
    )

    assert result["provider"] == "opencode-go"
    assert calls["kwargs"] == {
        "requested": "opencode-go",
        "target_model": "glm-5.1",
    }


def test_attempt_credential_self_heal_passes_target_model(monkeypatch):
    calls = {}
    fake_runtime_module = types.ModuleType("hermes_cli.runtime_provider")

    def fake_resolve_runtime_provider(**kwargs):
        calls["resolver_kwargs"] = kwargs
        return {
            "provider": kwargs.get("requested"),
            "base_url": "https://opencode.example.com/api",
            "api_key": "rt-key",
        }

    fake_runtime_module.resolve_runtime_provider = fake_resolve_runtime_provider

    def fake_runtime_lock(resolver, **kwargs):
        calls["lock_kwargs"] = kwargs
        return resolver(**kwargs)

    closed = []
    monkeypatch.setattr(api.oauth, "read_auth_json", lambda: {"providers": {"opencode-go": {}}})
    monkeypatch.setattr(
        api.oauth,
        "resolve_runtime_provider_with_anthropic_env_lock",
        fake_runtime_lock,
    )
    monkeypatch.setattr(
        api.config,
        "SESSION_AGENT_CACHE",
        {"sess-3895": object()},
    )
    monkeypatch.setattr(api.config, "SESSION_AGENT_CACHE_LOCK", threading.Lock())
    monkeypatch.setattr(api.config, "invalidate_credential_pool_cache", lambda provider_id: calls.setdefault("invalidated", []).append(provider_id))
    monkeypatch.setattr(
        streaming,
        "_close_cached_agent_entry_at_session_boundary",
        lambda session_id, entry: closed.append((session_id, entry)),
    )
    monkeypatch.setitem(sys.modules, "hermes_cli.runtime_provider", fake_runtime_module)

    result = streaming._attempt_credential_self_heal(
        "opencode-go",
        "sess-3895",
        None,
        target_model="glm-5.1",
    )

    assert result["base_url"] == "https://opencode.example.com/api"
    assert calls["lock_kwargs"] == {
        "requested": "opencode-go",
        "target_model": "glm-5.1",
    }
    assert calls["resolver_kwargs"] == {
        "requested": "opencode-go",
        "target_model": "glm-5.1",
    }
    assert calls["invalidated"] == ["opencode-go"]
    assert len(closed) == 1
    assert closed[0][0] == "sess-3895"
    assert api.config.SESSION_AGENT_CACHE == {}
