"""Concurrent SettingsWrite must not lose updates.

SettingsWrite is a read-modify-write that routes through update_settings (which
awaits), so two autonomous agents writing at the same time would interleave: each
loads the same snapshot, each writes the WHOLE object back, and the last writer
silently reverts the other's field, while BOTH agents are told "applied". This
drives two genuinely concurrent writes (different fields) through the ASGI app
and asserts neither is lost. It's the regression guard for the asyncio lock that
serializes these writes; without the lock this fails reproducibly.
"""

from __future__ import annotations

import asyncio

import httpx
import pytest

from backend.main import app


def p_auth_headers():
    import backend.auth as auth_mod
    if not auth_mod.TOKEN:
        import secrets
        auth_mod.TOKEN = secrets.token_urlsafe(32)
    return {"Authorization": f"Bearer {auth_mod.TOKEN}"}


@pytest.fixture
def reset_settings():
    from backend.apps.settings.settings import load_settings, save_settings
    original = load_settings().model_copy(deep=True)
    yield
    save_settings(original)


@pytest.mark.asyncio
async def test_concurrent_writes_to_different_fields_both_survive(reset_settings):
    from backend.apps.settings.settings import load_settings, save_settings

    base = load_settings()
    base.theme = "dark"
    base.default_mode = "agent"
    save_settings(base)

    headers = p_auth_headers()
    transport = httpx.ASGITransport(app=app)
    async with httpx.AsyncClient(transport=transport, base_url="http://test", headers=headers) as client:
        r1, r2 = await asyncio.gather(
            client.post("/api/settings-meta/write", json={"changes": {"theme": "light"}}),
            client.post("/api/settings-meta/write", json={"changes": {"default_mode": "chat"}}),
        )

    assert r1.status_code == 200 and r2.status_code == 200
    assert r1.json()["outcomes"]["theme"]["status"] == "applied"
    assert r2.json()["outcomes"]["default_mode"]["status"] == "applied"

    final = load_settings()
    # Both concurrent edits must persist; neither agent's "applied" result is a lie.
    assert final.theme == "light", "lost update: theme was clobbered by the concurrent write"
    assert final.default_mode == "chat", "lost update: default_mode was clobbered"
