'''
Pytest shared-memory ownership and leak-cleanup regressions.

'''
from collections.abc import Callable
from uuid import uuid4

import numpy as np
import pytest
from tractor._exceptions import NoRuntime
from tractor.ipc import _shm
from tractor.ipc._mp_bs import disable_mantracker

from piker.data._sharedmem import maybe_open_shm_array
from piker.data._source import def_iohlcv_fields


def test_shm_tracker_catches_pre_actor_failure(
    shm_leak_tracker: Callable[[], set[str]],
) -> None:
    '''
    Clean allocations which fail before actor-lifetime registration.

    ``open_shm_ndarray()`` creates its data and index segments before it
    asks ``current_actor()`` for the lifetime stack. Without a runtime,
    that lookup raises after all three OS names exist and the old harness
    leaked them. Reproduce that ordering through Piker's real wrapper,
    invoke tracked cleanup, and prove the data, first, and last segments
    plus their process-local token are all removed.

    '''
    key = f'test_shm_failure_{uuid4().hex}'
    with pytest.raises(NoRuntime):
        maybe_open_shm_array(
            key=key,
            size=4,
            dtype=np.dtype(def_iohlcv_fields),
        )

    token = _shm.NDToken.from_msg(_shm._known_tokens[key])
    names = {
        token.shm_name,
        token.shm_first_index_name,
        token.shm_last_index_name,
    }
    assert shm_leak_tracker() == names
    assert key not in _shm._known_tokens
    for name in names:
        with pytest.raises(FileNotFoundError):
            _shm.SharedMemory(
                name=name,
                create=False,
            )


def test_shm_leak_tracker_unlinks_only_owned_segments(
    shm_leak_tracker: Callable[[], set[str]],
) -> None:
    '''
    Test cleanup must unlink creators without harming attachments.

    A failed SHM allocation can escape before Tractor registers actor
    lifetime callbacks. Broad cache or filesystem cleanup is unsafe
    because tests may attach to a live Piker segment they do not own.
    Create one external segment through an unpatched factory, attach to
    it through Tractor, and create one test-owned segment through the
    tracked factory. Invoke cleanup and prove only the owned name was
    removed while the external segment remains attachable.

    '''
    open_external_shm = disable_mantracker()
    external = None
    attachment = None
    owned = None
    try:
        external = open_external_shm(
            create=True,
            size=8,
        )
        attachment = _shm.SharedMemory(
            name=external.name,
            create=False,
        )
        owned = _shm.SharedMemory(None, True, 8)
        cleaned: set[str] = shm_leak_tracker()

        assert cleaned == {owned.name}
        with pytest.raises(FileNotFoundError):
            _shm.SharedMemory(
                name=owned.name,
                create=False,
            )

        survivor = _shm.SharedMemory(
            name=external.name,
            create=False,
        )
        survivor.close()
    finally:
        if owned is not None:
            owned.close()
        if attachment is not None:
            attachment.close()
        if external is not None:
            try:
                external.unlink()
            except FileNotFoundError:
                pass
            external.close()
