'''
Real service-tree coverage for the offline replay provider.

'''
from contextlib import AbstractAsyncContextManager
from multiprocessing.shared_memory import SharedMemory
from pathlib import Path
from typing import (
    Any,
    Callable,
)

import msgspec
import numpy as np
import pytest
import tractor
import trio

from piker.brokers import replay
from piker.data import open_feed
from piker.service import Services


FQME: str = 'btcusd.test.replay'


async def _request(
    stream: tractor.MsgStream,
    command: replay.ReplayCommand,
) -> replay.ReplayAck:
    '''
    Send one replay command and receive its correlated reply.

    '''
    await stream.send(command)
    ack: replay.ReplayAck = await stream.receive()
    assert ack.command_id == command.command_id
    return ack


async def _search_replay_symbols(
    portal: tractor.Portal,
) -> dict[str, Any]:
    '''
    Exercise the backend's normal symbol-search context.

    '''
    ctx: tractor.Context
    async with portal.open_context(
        replay.open_symbol_search,
    ) as (ctx, _):
        async with ctx.open_stream() as stream:
            await stream.send('btc')
            return await stream.receive()


async def _run_basic_generation(
    open_test_pikerd: Callable[
        ...,
        AbstractAsyncContextManager,
    ],
    loglevel: str,
) -> tuple[bytes, list[dict[str, Any]], list[float]]:
    '''
    Run one complete replay through real data services.

    '''
    quote_transcript: list[dict[str, Any]] = []
    shm_names: list[str] = []
    services: Services
    async with (
        open_test_pikerd() as (_, _, _, services),
        open_feed(
            [FQME],
            loglevel=loglevel,
        ) as feed,
    ):
        assert 'datad.replay' in services.service_tasks
        assert 'samplerd' in services.service_tasks

        stream: tractor.MsgStream = feed.streams['replay']
        first_msg: dict[str, Any] = await stream.receive()
        quote_transcript.append(first_msg)
        assert first_msg[FQME]['replay_seq'] == 1

        flume = feed.flumes[FQME]
        history_closes: list[float] = (
            flume.hist_shm.array['close'].tolist()
        )
        shm_names.extend([
            flume.hist_shm._shm.name,
            flume.rt_shm._shm.name,
        ])
        assert history_closes == [
            99.2,
            99.4,
            99.6,
            99.8,
            100.2,
            100.6,
        ]

        portal: tractor.Portal = feed.portals[replay]
        matches: dict[str, Any] = await _search_replay_symbols(
            portal
        )
        assert list(matches) == [FQME]

        control_ctx: tractor.Context
        started: replay.ReplaySnapshot
        async with portal.open_context(
            replay.open_replay_control,
        ) as (control_ctx, started):
            assert started.state == 'ready'
            assert started.event_sequence == 1

            async with control_ctx.open_stream() as control:
                with control_ctx.pld_rx.limit_plds(
                    spec=replay.ReplayAck,
                ):
                    await feed.pause()
                    paused: replay.ReplayAck = await _request(
                        control,
                        replay.AwaitState(
                            command_id=1,
                            state='paused',
                        ),
                    )
                    assert paused.ok
                    assert not paused.snapshot.subscriber_active

                    rejected: replay.ReplayAck = await _request(
                        control,
                        replay.Advance(
                            command_id=2,
                            event_sequence=2,
                        ),
                    )
                    assert not rejected.ok
                    assert rejected.snapshot.event_sequence == 1

                    await feed.resume()
                    ready: replay.ReplayAck = await _request(
                        control,
                        replay.AwaitState(
                            command_id=3,
                            state='ready',
                        ),
                    )
                    assert ready.ok
                    assert ready.snapshot.subscriber_active

                    producer_paused = await _request(
                        control,
                        replay.Pause(command_id=4),
                    )
                    assert producer_paused.snapshot.state == 'paused'
                    gate_rejected = await _request(
                        control,
                        replay.Advance(
                            command_id=5,
                            event_sequence=2,
                        ),
                    )
                    assert not gate_rejected.ok
                    producer_ready = await _request(
                        control,
                        replay.Resume(command_id=6),
                    )
                    assert producer_ready.snapshot.state == 'ready'

                    for command_id, event_sequence in (
                        (7, 2),
                        (8, 3),
                        (9, 4),
                    ):
                        advanced: replay.ReplayAck = (
                            await _request(
                                control,
                                replay.Advance(
                                    command_id=command_id,
                                    event_sequence=(
                                        event_sequence
                                    ),
                                ),
                            )
                        )
                        assert advanced.ok
                        quote: dict[str, Any] = (
                            await stream.receive()
                        )
                        quote_transcript.append(quote)
                        assert (
                            quote[FQME]['replay_seq']
                            == event_sequence
                        )

                    snap_ack: replay.ReplayAck = await _request(
                        control,
                        replay.Snapshot(command_id=10),
                    )
                    assert snap_ack.snapshot.state == 'exhausted'
                    transcript: bytes = msgspec.json.encode(
                        snap_ack.snapshot
                    )

        np.testing.assert_allclose(
            flume.rt_shm.array['close'][-1],
            101.0,
        )

    assert not services.service_tasks
    for shm_name in shm_names:
        with pytest.raises(FileNotFoundError):
            SharedMemory(name=shm_name)

    return transcript, quote_transcript, history_closes


@pytest.mark.parametrize(
    'replay_scenario',
    ['basic-v1'],
    indirect=True,
)
def test_real_feed_replay_is_repeatable(
    replay_scenario: Path,
    open_test_pikerd: Callable[
        ...,
        AbstractAsyncContextManager,
    ],
    loglevel: str,
) -> None:
    '''
    Replay identical typed transcripts through fresh actor trees.

    Mocked feed calls can hide backend discovery, datad and samplerd
    startup, history publication, SHM writes, stream subscription
    control, and context teardown failures. Run the same tracked
    scenario through two fresh `open_test_pikerd()` generations.
    Each generation receives the initial quote, pauses the production
    feed, awaits the typed subscriber-state barrier, proves
    advancement is rejected, resumes, repeats that gate check
    through typed producer controls, and explicitly advances every
    remaining event. Exact control bytes, quote dictionaries, and
    history values must match, while service and named-SHM checks
    prove each generation fully tears down before the next one
    starts.

    '''
    assert replay_scenario.name == 'basic-v1.json'

    async def main() -> None:
        first = await _run_basic_generation(
            open_test_pikerd,
            loglevel,
        )
        second = await _run_basic_generation(
            open_test_pikerd,
            loglevel,
        )
        assert first == second

    trio.run(main)


@pytest.mark.parametrize(
    'replay_scenario',
    ['failure-v1'],
    indirect=True,
)
def test_failure_injection_is_acknowledged(
    replay_scenario: Path,
    open_test_pikerd: Callable[
        ...,
        AbstractAsyncContextManager,
    ],
    loglevel: str,
) -> None:
    '''
    Stop at a fixture failure without publishing its quote.

    An injected provider disconnect used to require timing a task
    crash and inferring its position from logs. Start the real feed,
    arm the fixture's third event through typed control, publish
    event two, then request event three. Its correlated negative
    acknowledgement must identify the fixture error, retain event
    sequence two, and expose `failed` state. Reading SHM after the
    acknowledgement proves the rejected event was not hidden in the
    sampler before normal actor and SHM teardown.

    '''
    assert replay_scenario.name == 'failure-v1.json'

    async def main() -> None:
        services: Services
        shm_names: list[str] = []
        async with (
            open_test_pikerd() as (_, _, _, services),
            open_feed(
                [FQME],
                loglevel=loglevel,
            ) as feed,
        ):
            quotes: tractor.MsgStream = feed.streams['replay']
            first: dict[str, Any] = await quotes.receive()
            assert first[FQME]['replay_seq'] == 1

            flume = feed.flumes[FQME]
            shm_names.extend([
                flume.hist_shm._shm.name,
                flume.rt_shm._shm.name,
            ])
            portal: tractor.Portal = feed.portals[replay]
            ctx: tractor.Context
            async with portal.open_context(
                replay.open_replay_control,
            ) as (ctx, _):
                async with ctx.open_stream() as control:
                    with ctx.pld_rx.limit_plds(
                        spec=replay.ReplayAck,
                    ):
                        armed: replay.ReplayAck = await _request(
                            control,
                            replay.FailAt(
                                command_id=1,
                                event_sequence=3,
                            ),
                        )
                        assert armed.ok

                        advanced: replay.ReplayAck = (
                            await _request(
                                control,
                                replay.Advance(
                                    command_id=2,
                                    event_sequence=2,
                                ),
                            )
                        )
                        assert advanced.ok
                        second: dict[str, Any] = (
                            await quotes.receive()
                        )
                        assert second[FQME]['replay_seq'] == 2

                        failed: replay.ReplayAck = await _request(
                            control,
                            replay.Advance(
                                command_id=3,
                                event_sequence=3,
                            ),
                        )
                        assert not failed.ok
                        assert failed.snapshot.state == 'failed'
                        assert failed.snapshot.event_sequence == 2
                        assert failed.error == (
                            'fixture_disconnect: offline provider '
                            'disconnected'
                        )
                        assert (
                            flume.rt_shm.array['close'][-1]
                            == 100.5
                        )

        assert not services.service_tasks
        for shm_name in shm_names:
            with pytest.raises(FileNotFoundError):
                SharedMemory(name=shm_name)

    trio.run(main)
