"""Shared fixtures for behavioral specs.

These tests are the living specification of the Nostr→DuckDB ingest pipeline.
Each test stands up its own fresh DuckDB file (via ``AdminCore`` on a temp
path) and, where applicable, its own ``MockRelay`` instance — there is no
shared state between tests by design.

The fixtures here build an ``AdminCore`` but DO NOT call ``core.start()``.
Each test starts whichever lifecycle pieces it actually needs (often just
``core.boot()`` for relay-bus traffic, sometimes none at all for pure
storage-layer specs). Keeping start-up explicit makes each test read like a
self-contained behavior description.
"""

from __future__ import annotations

import json
import os
import tempfile

import pytest

from admin_core import AdminCore
from mock_relay import MockRelay

# Side-effect import: attaches assertion methods (e.g. ``expect_open_req``)
# onto MockRelay so spec tests can call ``mock_relay.expect_open_req({...})``.
from . import helpers as _helpers  # noqa: F401


# Auto-tracker tick used by specs. Production default is 30s — way too slow
# for tests that want to observe a fresh subscribe being picked up. 0.1s gives
# the loop ~10 chances per second to claim a new ``last_scan_at IS NULL`` row.
SPEC_AUTO_TRACK_INTERVAL_S = 0.1


@pytest.fixture
def core():
    """Fresh ``AdminCore`` backed by a temp DuckDB file with a fast
    auto-tracker tick. No threads started — useful for specs that just
    assert table state after a tool call. Teardown calls ``core.stop()``
    unconditionally (idempotent, safe even if nothing was started).
    """
    with tempfile.TemporaryDirectory() as td:
        c = AdminCore(
            os.path.join(td, "spec.duckdb"),
            auto_track_interval_s=SPEC_AUTO_TRACK_INTERVAL_S,
        )
        try:
            yield c
        finally:
            c.stop()


@pytest.fixture
def running_core(core):
    """``core`` with ``start()`` already called — bus + auto-tracker live.
    Use in specs that need the live ingest pipeline (relay traffic, auto-
    discovery, live subscriptions). The ``core`` fixture's teardown still
    handles ``stop()``.
    """
    core.start()
    return core


@pytest.fixture
def mock_relay(core):
    """A MockRelay registered in the ``relays`` table of ``core``'s DuckDB.

    Default handler replies to every REQ with an EOSE so discovery probes
    finish promptly without surfacing any events. Tests that need to push
    EVENT frames build their own MockRelay with a custom handler instead.
    """
    async def handler(relay, ws, msg):
        if msg[0] == "REQ":
            sub_id = msg[1]
            for evt in list(relay.staged_events):
                await ws.send(json.dumps(["EVENT", sub_id, evt]))
            await ws.send(json.dumps(["EOSE", sub_id]))

    r = MockRelay(handler)
    r.start()
    core.add_relay(r.url)
    try:
        yield r
    finally:
        r.stop()
