'''
Regression tests for the cold package import surface.

'''
import json
import os
from statistics import median
import subprocess
import sys
from typing import (
    Any,
    get_type_hints,
)

from tractor.discovery import (
    _addr,
    _multiaddr,
)
from tractor.ipc import (
    _tcp,
    _uds,
)


def run_cold_import(code: str) -> dict[str, object]:
    result = subprocess.run(
        [
            sys.executable,
            '-c',
            code,
        ],
        check=True,
        capture_output=True,
        text=True,
    )
    return json.loads(result.stdout)


def test_lazy_to_asyncio_package_api():
    '''
    Keep the public lazy submodule discoverable without eagerly
    importing it.

    Before the lazy conversion, package import side effects exposed
    `to_asyncio` to `dir()` and wildcard imports. Exercise those APIs
    in cold interpreters so this test proves normal `import tractor`
    leaves `asyncio` unloaded, while discovery and wildcard access
    still advertise and resolve the public submodule.

    '''
    cold = run_cold_import(
        'import json, sys, tractor; '
        'print(json.dumps({'
        '"advertised": "to_asyncio" in dir(tractor), '
        '"asyncio_loaded": "asyncio" in sys.modules}))'
    )
    assert cold == {
        'advertised': True,
        'asyncio_loaded': False,
    }

    wildcard = run_cold_import(
        'import json; '
        'from tractor import *; '
        'print(json.dumps({'
        '"module": to_asyncio.__name__}))'
    )
    assert wildcard == {
        'module': 'tractor.to_asyncio',
    }


def test_cold_import_budget():
    '''
    Keep cold package import below the pre-optimization regression.

    The original `inspect.stack()` caller lookup made a fresh
    `import tractor` take about 0.42s and dominate actor startup.
    Run seven independent interpreters and gate their median at a
    deliberately broad 0.35s: over twice the measured ~0.145s
    baseline, but low enough to catch restoration of that hot path.

    Taking the median absorbs process-start and shared-runner noise.
    The child measures only its import, rather than parent-side
    process creation. `TRACTOR_IMPORT_BUDGET_S` provides an explicit,
    reviewable override for platforms that establish a different
    baseline instead of silently weakening the project default.

    Each child also reports the modules whose eager loading this PR
    intentionally removes, proving a timing pass cannot hide a
    dependency-import regression.

    '''
    budget_s = float(
        os.environ.get(
            'TRACTOR_IMPORT_BUDGET_S',
            '0.35',
        )
    )
    optional_mods = (
        'asyncio',
        'bidict',
        'colorlog',
        'multiaddr',
        'wrapt',
    )
    code = (
        'import json, sys, time; '
        'started = time.perf_counter(); '
        'import tractor; '
        'elapsed = time.perf_counter() - started; '
        f'optional = {optional_mods!r}; '
        'print(json.dumps({'
        '"elapsed": elapsed, '
        '"loaded": [name for name in optional '
        'if name in sys.modules]}))'
    )
    samples = [
        run_cold_import(code)
        for _ in range(7)
    ]
    elapsed = [
        float(sample['elapsed'])
        for sample in samples
    ]
    loaded = {
        name
        for sample in samples
        for name in sample['loaded']
    }

    assert not loaded
    assert median(elapsed) < budget_s, (
        f'cold import median exceeded {budget_s:.3f}s budget: '
        f'{elapsed!r}'
    )


def test_lazy_annotation_names_resolve():
    '''
    Resolve annotations without importing optional dependencies.

    Moving annotation-only third-party names under `TYPE_CHECKING`
    left their runtime globals undefined, causing
    `typing.get_type_hints()` to raise `NameError`. Resolve every
    affected API and prove the lazy aliases retain import-free runtime
    introspection.

    '''
    assert get_type_hints(_multiaddr.mk_maddr)['return'] is Any
    assert get_type_hints(_tcp.MsgpackTCPStream.maddr.fget)[
        'return'
    ] is Any
    assert get_type_hints(_uds.MsgpackUDSStream.maddr.fget)[
        'return'
    ] == Any|str
    assert get_type_hints(_addr.Address.get_random)[
        'current_actor'
    ] is Any
    assert _addr.__annotations__['_address_types'].startswith('dict')
