"""Pipeline run-id context and the scanner stamping it into scan markers."""

from __future__ import annotations

import asyncio

import pytest

from app.services import pipeline_run


def test_no_run_id_by_default():
    assert pipeline_run.current() is None


def test_bind_and_release_restore_previous():
    assert pipeline_run.current() is None
    token = pipeline_run.bind("run-1")
    try:
        assert pipeline_run.current() == "run-1"
    finally:
        pipeline_run.release(token)
    assert pipeline_run.current() is None


def test_new_run_ids_are_unique():
    ids = {pipeline_run.new_run_id() for _ in range(100)}
    assert len(ids) == 100


@pytest.mark.asyncio
async def test_run_id_propagates_to_awaited_coroutines():
    """The scan and shadow steps are awaited inside the pipeline's task, so they
    must observe the id the pipeline bound."""

    async def step() -> str | None:
        return pipeline_run.current()

    token = pipeline_run.bind("run-42")
    try:
        assert await step() == "run-42"
    finally:
        pipeline_run.release(token)


@pytest.mark.asyncio
async def test_run_id_does_not_leak_into_an_independent_task():
    """A manual scan is a separate APScheduler job, started independently of the
    pipeline. Modelled here as a task created before the bind: it captures its
    own context and never observes the id the pipeline binds afterwards."""
    seen: dict[str, str | None] = {}
    manual_started = asyncio.Event()
    let_manual_finish = asyncio.Event()

    async def manual_job() -> None:
        manual_started.set()
        await let_manual_finish.wait()
        seen["manual"] = pipeline_run.current()

    # Created with no id in context — the manual job predates the pipeline bind.
    task = asyncio.create_task(manual_job())
    await manual_started.wait()

    token = pipeline_run.bind("pipeline")
    try:
        assert pipeline_run.current() == "pipeline"
        let_manual_finish.set()
        await task
    finally:
        pipeline_run.release(token)

    assert seen["manual"] is None
