"""Moving a rotating credential out of 9Router's db.json without racing 9Router for the file.

The stakes: providers issue a NEW refresh token on every refresh and treat a replayed one as
theft, revoking the whole grant family. So exactly one holder may be able to refresh. Removing
`refreshToken` from a connection is what makes an instance structurally unable to rotate. If that
edit were lost to a concurrent router write, or if the router resurrected the token afterwards,
we would have two rotators and a dead account.

Run:
    cd backend && .venv/bin/python -m pytest tests/test_credential_store.py -v
"""

from __future__ import annotations

import json
import os
import stat

import pytest

import backend.apps.nine_router.credential_store as store
from backend.apps.nine_router import process

P_CONNECTION = {
    "id": "conn-1",
    "provider": "claude",
    "authType": "oauth",
    "accessToken": "access-value",
    "refreshToken": "refresh-value",
    "expiresAt": "2026-08-01T00:00:00.000Z",
    "isActive": True,
}


@pytest.fixture
def p_router(tmp_path, monkeypatch):
    """A stopped router with one oauth connection on disk. Restart is recorded, never real."""
    data_dir = tmp_path / "9router"
    data_dir.mkdir()
    db = {"providerConnections": [dict(P_CONNECTION), {"id": "conn-2", "authType": "apikey"}]}
    (data_dir / "db.json").write_text(json.dumps(db))
    monkeypatch.setattr(process, "nine_router_data_dir", lambda: str(data_dir))

    state = {"running": True, "restarts": 0}

    def p_stop() -> None:
        state["running"] = False

    async def p_ensure() -> None:
        state["restarts"] += 1
        state["running"] = True

    async def p_no_http() -> None:
        state["shutdown_calls"] += 1

    state["shutdown_calls"] = 0
    monkeypatch.setattr(process, "stop", p_stop)
    monkeypatch.setattr(process, "is_running", lambda: state["running"])
    monkeypatch.setattr(process, "ensure_running", p_ensure)
    # Hard-stubbed: without this the suite would POST /shutdown at whatever real router owns the port.
    monkeypatch.setattr(store, "request_shutdown", p_no_http)
    return state


def p_connection(data_dir_owner) -> dict:
    db = json.loads(open(store.db_path(), encoding="utf-8").read())
    return next(c for c in db["providerConnections"] if c["id"] == "conn-1")


def test_read_credential_returns_the_tokens(p_router):
    cred = store.read_credential("conn-1")
    assert cred is not None
    assert cred.provider == "claude"
    assert cred.access_token == "access-value"
    assert cred.refresh_token == "refresh-value"


def test_only_oauth_connections_are_listed(p_router):
    assert store.list_oauth_connection_ids() == ["conn-1"]


@pytest.mark.asyncio
async def test_dropping_the_refresh_token_removes_the_key(p_router):
    """Absent, not blank. 9Router's refresh dispatcher bails on a falsy refreshToken, so the key
    being gone is precisely what makes this instance unable to rotate."""
    ok = await store.apply_to_connection("conn-1", changes={}, drop=["refreshToken"])
    assert ok
    after = p_connection(p_router)
    assert "refreshToken" not in after
    assert after["accessToken"] == "access-value", "must not disturb the rest of the connection"


@pytest.mark.asyncio
async def test_router_is_stopped_then_restarted(p_router):
    await store.apply_to_connection("conn-1", changes={}, drop=["refreshToken"])
    assert p_router["shutdown_calls"] == 1, "an adopted router only goes down over HTTP"
    assert p_router["restarts"] == 1
    assert p_router["running"] is True


@pytest.mark.asyncio
async def test_refuses_to_edit_when_the_router_will_not_stop(p_router, monkeypatch):
    """The load-bearing guard. Editing under a live router risks losing the edit, or worse, the
    router rewriting the refresh token back after we have already handed it to the cloud."""
    monkeypatch.setattr(process, "is_running", lambda: True)
    monkeypatch.setattr(process, "stop", lambda: None)
    monkeypatch.setattr(store, "P_DOWN_WAIT_S", 0.2)

    ok = await store.apply_to_connection("conn-1", changes={}, drop=["refreshToken"])

    assert ok is False
    assert p_connection(p_router)["refreshToken"] == "refresh-value", "file must be untouched"


@pytest.mark.asyncio
async def test_restoring_a_refresh_token_round_trips(p_router):
    await store.apply_to_connection("conn-1", changes={}, drop=["refreshToken"])
    ok = await store.apply_to_connection("conn-1", changes={"refreshToken": "returned"}, drop=[])
    assert ok
    assert p_connection(p_router)["refreshToken"] == "returned"


@pytest.mark.asyncio
async def test_written_db_is_owner_only(p_router):
    await store.apply_to_connection("conn-1", changes={}, drop=["refreshToken"])
    mode = stat.S_IMODE(os.stat(store.db_path()).st_mode)
    assert mode & 0o077 == 0


@pytest.mark.asyncio
async def test_unknown_connection_changes_nothing(p_router):
    ok = await store.apply_to_connection("conn-missing", changes={"x": 1}, drop=[])
    assert ok is False
    assert p_connection(p_router)["refreshToken"] == "refresh-value"


@pytest.mark.asyncio
async def test_corrupt_db_is_not_overwritten(p_router):
    open(store.db_path(), "w", encoding="utf-8").write("{not json")
    ok = await store.apply_to_connection("conn-1", changes={}, drop=["refreshToken"])
    assert ok is False
    assert open(store.db_path(), encoding="utf-8").read() == "{not json"
