"""LIVE E2E: Desktop/TUI ``session.resume`` with a stale (deleted/renamed)
custom provider, through the REAL gateway dispatch path.

This is the live-report shape from Discord (endpoints "deleted ages ago"
coming back, Bot Chats pinned to dead providers): a real ``state.db`` session
row whose persisted ``model_config`` names a custom provider that no longer
exists in config.yaml, resumed through ``tui_gateway.server.handle_request``
(the same dispatch the Desktop WebSocket transport funnels into) with
``eager_build: true`` so the REAL ``_make_agent`` → ``AIAgent`` construction
runs synchronously — no mocks, no pre-seeded sessions.

On pre-fix main the eager build dies with
``resume failed: Unknown provider 'custom:oldone'`` (or silently pins the
dead identity). Post-fix:

* renamed provider (same endpoint/model under a new name) → healed to the
  new ``custom:<name>`` identity;
* deleted, unrecoverable provider → falls back to the configured default;
* legacy canonical Bot Chat row (titled exactly "Bot Chat", no
  follow_profile_config marker) → stored pin ignored, profile's CURRENT
  config used.

Run:  python -m pytest tests/tui_gateway/test_stale_provider_resume_live.py -o addopts= -v -s
"""

from __future__ import annotations

import json
import os
import tempfile
import uuid
from pathlib import Path

import pytest
import hermes_yaml as yaml

OLD_URL = "https://old-endpoint.invalid/v1"
NEW_URL = "https://new-endpoint.invalid/v1"


@pytest.fixture()
def live_home(monkeypatch):
    """A REAL isolated HERMES_HOME with a config.yaml + state.db on disk."""
    tmp = Path(tempfile.mkdtemp(prefix="hermes-live-staleprov-"))
    home = tmp / ".hermes"
    home.mkdir(parents=True)
    config = {
        "model": {"default": "test-model-live", "provider": "custom:newone"},
        "custom_providers": [
            {
                "name": "newone",
                "base_url": NEW_URL,
                "api_key": "sk-live-test-not-real",
                "api_mode": "chat_completions",
            }
        ],
    }
    (home / "config.yaml").write_text(yaml.safe_dump(config))
    monkeypatch.setenv("HERMES_HOME", str(home))
    # hermes_constants caches the resolved home at first read — the env var
    # alone doesn't repoint an already-imported process. Use the override API
    # (the same mechanism profile-scoped resumes use).
    from hermes_constants import reset_hermes_home_override, set_hermes_home_override

    home_token = set_hermes_home_override(str(home))
    # Neutralize ambient provider creds so resolution uses ONLY the config
    # above — this must behave the same on a dev box and a bare CI runner.
    for var in list(os.environ):
        if var.endswith("_API_KEY") or var in ("OPENROUTER_KEY", "NOUS_KEY"):
            monkeypatch.delenv(var, raising=False)

    import hermes_cli.config as hconfig
    import hermes_cli.runtime_provider as rp

    for mod in (hconfig, rp):
        for attr in ("_config_cache", "_cache", "_CONFIG_CACHE"):
            if hasattr(mod, attr):
                try:
                    setattr(mod, attr, None)
                except Exception:
                    pass

    import hermes_state
    import tui_gateway.server as server

    # The launch DB handle and the module-level home snapshot are import-time
    # caches — repoint both at the isolated home for the duration of the test
    # (see references: tui-gateway live WS harness, same trap). The hermetic
    # conftest also re-pins hermes_state.DEFAULT_DB_PATH per test; pin it to
    # THIS home so server._get_db() opens the same real state.db we seed.
    monkeypatch.setattr(hermes_state, "DEFAULT_DB_PATH", home / "state.db")
    monkeypatch.setattr(server, "_db", None, raising=False)
    monkeypatch.setattr(server, "_db_error", None, raising=False)
    monkeypatch.setattr(server, "_hermes_home", str(home), raising=False)
    yield home, server
    # Detach the shared handle so the tmpdir can be reclaimed.
    try:
        if server._db is not None:
            server._db.close()
    except Exception:
        pass
    server._db = None
    try:
        reset_hermes_home_override(home_token)
    except Exception:
        pass


def _seed_session_row(
    home: Path,
    *,
    title: str,
    provider: str,
    base_url: str | None = OLD_URL,
    extra_config: dict | None = None,
) -> str:
    """Write a REAL session row + one message into the profile's state.db."""
    from hermes_state import SessionDB

    db = SessionDB(db_path=home / "state.db")
    sid = uuid.uuid4().hex[:12]
    model_config = {
        "model": "test-model-live",
        "provider": provider,
        "api_mode": "chat_completions",
    }
    if base_url:
        model_config["base_url"] = base_url
    if extra_config:
        model_config.update(extra_config)
    db.create_session(
        sid,
        source="tui",
        model="test-model-live",
        model_config=model_config,
        session_key=f"live-test:{sid}",
    )
    db.set_session_title(sid, title)
    db.close()
    return sid


def _resume(server, sid: str) -> dict:
    """Drive the REAL dispatch entry (same funnel the Desktop WS uses)."""
    return server.handle_request(
        {
            "id": f"rid-{sid}",
            "method": "session.resume",
            "params": {"session_id": sid, "eager_build": True, "omit_messages": True},
        }
    )


def _teardown(server, resp) -> None:
    live_sid = (resp.get("result") or {}).get("session_id")
    if live_sid:
        server.handle_request(
            {"id": "close", "method": "session.close", "params": {"session_id": live_sid}}
        )


class TestStaleProviderResumeLive:
    def test_deleted_provider_falls_back_to_default(self, live_home):
        """The Discord report shape: the stored provider was deleted from
        config ages ago. Resume must NOT die with 'Unknown provider' — it
        falls back to the configured default and the agent builds."""
        home, server = live_home
        sid = _seed_session_row(
            home, title="Old work chat", provider="custom:deleted-ages-ago"
        )
        resp = _resume(server, sid)
        try:
            assert "error" not in resp, f"resume failed live: {resp.get('error')}"
            result = resp["result"]
            assert result.get("resumed") == sid
            info = result.get("info") or {}
            # Healed via base_url→entry recovery or dropped to the configured
            # default — either way the DEAD name must not survive.
            assert "deleted-ages-ago" not in json.dumps(info)
        finally:
            _teardown(server, resp)

    def test_renamed_provider_heals_to_new_identity(self, live_home):
        """oldone → newone rename, same model: identity heals to newone."""
        home, server = live_home
        sid = _seed_session_row(
            home, title="Renamed provider chat", provider="custom:oldone", base_url=None
        )
        resp = _resume(server, sid)
        try:
            assert "error" not in resp, f"resume failed live: {resp.get('error')}"
        finally:
            _teardown(server, resp)

    def test_legacy_bot_chat_follows_current_profile_config(self, live_home):
        """A pre-contract canonical Bot Chat (title exactly 'Bot Chat', no
        follow_profile_config marker) pinned to a dead provider must resume
        on the profile's CURRENT config."""
        home, server = live_home
        sid = _seed_session_row(home, title="Bot Chat", provider="custom:deadbot")
        resp = _resume(server, sid)
        try:
            assert "error" not in resp, f"Bot Chat resume failed live: {resp.get('error')}"
            info = (resp.get("result") or {}).get("info") or {}
            assert "deadbot" not in json.dumps(info)
        finally:
            _teardown(server, resp)
