"""Regression test for #1353: layer timelapse must start for queue/VP-dispatched prints.

Reporter @Andlar94 ran the external-camera flow on an A1 dispatched via the
print queue (so each print landed in the on_print_start "expected archive"
branch). Frames were never captured, no MP4 was produced, yet the post-print
log line said "Stitching layer timelapse for printer 1" — because
`tl_complete()` ran, found no active session, and silently returned None.

Root cause: only the two new-archive code paths in on_print_start
(`fallback_archive` + `archive_print`) called `layer_timelapse.start_session`.
The expected-archive branch — where reprints and queue dispatch land —
updated the existing archive's status to "printing" but never started a
timelapse session.

Fix: the three start_session call sites in on_print_start were unified
behind `_maybe_start_layer_timelapse(printer, printer_id, archive_id)`,
which gates on the same `external_camera_enabled and external_camera_url`
check. Testing the helper directly (instead of driving the whole
on_print_start flow) keeps this regression locked in without dragging in
unrelated side effects (plate detection, DB queries, MQTT relay, etc.).
"""

from types import SimpleNamespace
from unittest.mock import patch

from backend.app.main import _maybe_start_layer_timelapse


def _make_printer(*, external_camera_enabled: bool, external_camera_url: str | None):
    """Construct a minimal printer-shaped object with exactly the attributes
    the helper reads. SimpleNamespace is used over MagicMock so attribute
    access raises AttributeError on anything unexpected — keeps the test
    honest about which fields the helper actually depends on.
    """
    return SimpleNamespace(
        external_camera_enabled=external_camera_enabled,
        external_camera_url=external_camera_url,
        external_camera_type="snapshot",
        external_camera_snapshot_url=external_camera_url,
    )


def test_starts_timelapse_when_external_camera_enabled():
    """Queue/VP-dispatched prints land in the expected-archive branch and must
    start the timelapse session there (the #1353 root cause). The helper is
    called from all three on_print_start paths (expected-archive promotion,
    fallback archive, fresh archive) so testing it once covers all three."""
    printer = _make_printer(
        external_camera_enabled=True,
        external_camera_url="http://camera.local:5000/snapshot.jpg",
    )

    with patch("backend.app.services.layer_timelapse.start_session") as mock_start_session:
        started = _maybe_start_layer_timelapse(printer, printer_id=1, archive_id=42)

    assert started is True
    mock_start_session.assert_called_once_with(
        1,
        42,
        "http://camera.local:5000/snapshot.jpg",
        "snapshot",
        snapshot_url="http://camera.local:5000/snapshot.jpg",
        rotation=0,
    )


def test_skips_timelapse_when_external_camera_disabled():
    """The same guard that the new-archive paths use must hold here: no
    external camera → no timelapse session. Otherwise we'd try to capture
    from a None URL and crash the print-start flow."""
    printer = _make_printer(external_camera_enabled=False, external_camera_url=None)

    with patch("backend.app.services.layer_timelapse.start_session") as mock_start_session:
        started = _maybe_start_layer_timelapse(printer, printer_id=1, archive_id=99)

    assert started is False
    mock_start_session.assert_not_called()


def test_skips_timelapse_when_url_missing_even_if_flag_set():
    """If the toggle is on but the URL field is empty (legacy / half-configured
    install), the guard must still hold — calling start_session with an empty
    URL would crash downstream when the capture thread tries to fetch frames."""
    printer = _make_printer(external_camera_enabled=True, external_camera_url=None)

    with patch("backend.app.services.layer_timelapse.start_session") as mock_start_session:
        started = _maybe_start_layer_timelapse(printer, printer_id=1, archive_id=7)

    assert started is False
    mock_start_session.assert_not_called()


def test_camera_type_defaults_to_mjpeg_when_unset():
    """external_camera_type defaults to 'mjpeg' in start_session when the
    printer column is None — pre-existing contract preserved by the helper."""
    printer = SimpleNamespace(
        external_camera_enabled=True,
        external_camera_url="http://cam/feed",
        external_camera_type=None,
        external_camera_snapshot_url=None,
    )

    with patch("backend.app.services.layer_timelapse.start_session") as mock_start_session:
        _maybe_start_layer_timelapse(printer, printer_id=2, archive_id=11)

    assert mock_start_session.called
    call_kwargs = mock_start_session.call_args.kwargs
    call_args = mock_start_session.call_args.args
    assert call_args[3] == "mjpeg"
    assert call_kwargs["snapshot_url"] is None
