"""Auto-wrap sliced gcode as a Bambu-compatible 3MF.

Bambu firmware ignores the ``gcode_file`` MQTT command for raw .gcode
files — it only starts prints via ``project_file`` on .3mf.  Before
this wire-up, calling ``slice_model(printer_id='bambu_a1', ...)``
returned raw gcode and the agent had to know the Bambu-specific calling
convention + manually invoke ``wrap_gcode_as_3mf`` before starting a
print.  The user caught this ("kiln shoulda known to send a 3mf
request").

These tests cover the new ``_auto_wrap_bambu_3mf`` helper that:
* detects a Bambu printer_id
* invokes the existing Bambu 3MF repackager
* returns both the raw gcode and the wrapped 3MF paths in the response
* fails gracefully (non-fatal warning) when the repackager errors
"""
from __future__ import annotations

from pathlib import Path

import pytest

from kiln.plugins.slicer_tools import _auto_wrap_bambu_3mf


def _write_dummy_gcode(tmp_path: Path) -> Path:
    """Write a minimal PrusaSlicer-style gcode with a LAYER_CHANGE
    marker so build_bambu_3mf can parse it (it requires at least one
    layer).  We don't care if the 3MF is valid for actual printing —
    only that the wrap logic fires for Bambu printer_ids.
    """
    p = tmp_path / "dummy.gcode"
    p.write_text(
        "; Generated by PrusaSlicer (test fixture)\n"
        "; layer_height = 0.2\n"
        "; filament_type = PLA\n"
        ";LAYER_CHANGE\n;Z:0.2\n"
        "G28\nG1 X0 Y0 Z0.2 F3000\nG1 X10 Y10 E0.5 F1200\n"
        "; estimated printing time (normal mode) = 30m 0s\n"
    )
    return p


class TestBambuIdDetection:
    def test_bambu_a1_triggers_wrap(self, tmp_path: Path, monkeypatch) -> None:
        gcode = _write_dummy_gcode(tmp_path)
        # Stub build_bambu_3mf: first arg is the gcode BODY string (not
        # a path), second arg is the output 3MF path.
        import kiln.printers.bambu_3mf as _b3mf
        captured = {}
        def fake_build(body, dst, **kwargs):
            captured["body"] = body
            captured["dst"] = dst
            Path(dst).write_bytes(b"PK\x03\x04fake3mf")
        monkeypatch.setattr(_b3mf, "build_bambu_3mf", fake_build)

        threemf, warning = _auto_wrap_bambu_3mf(
            str(gcode), effective_printer_id="bambu_a1", stl_path=None,
        )
        assert threemf is not None
        assert Path(threemf).exists()
        assert warning is None
        # The gcode body was loaded from the source gcode path
        assert "PrusaSlicer" in captured["body"]

    def test_bambu_x1c_also_triggers_wrap(self, tmp_path: Path, monkeypatch) -> None:
        gcode = _write_dummy_gcode(tmp_path)
        import kiln.printers.bambu_3mf as _b3mf
        def ok(src, dst, **kwargs):
            Path(dst).write_bytes(b"PK\x03\x04")
        monkeypatch.setattr(_b3mf, "build_bambu_3mf", ok)

        threemf, _ = _auto_wrap_bambu_3mf(
            str(gcode), effective_printer_id="bambu_x1c", stl_path=None,
        )
        assert threemf is not None


class TestNonBambuSkipsWrap:
    def test_prusa_mini_no_wrap(self, tmp_path: Path) -> None:
        gcode = _write_dummy_gcode(tmp_path)
        threemf, warning = _auto_wrap_bambu_3mf(
            str(gcode), effective_printer_id="prusa_mini", stl_path=None,
        )
        assert threemf is None
        assert warning is None

    def test_ender3_no_wrap(self, tmp_path: Path) -> None:
        gcode = _write_dummy_gcode(tmp_path)
        threemf, warning = _auto_wrap_bambu_3mf(
            str(gcode), effective_printer_id="ender3", stl_path=None,
        )
        assert threemf is None
        assert warning is None

    def test_none_printer_id_no_wrap(self, tmp_path: Path) -> None:
        gcode = _write_dummy_gcode(tmp_path)
        threemf, warning = _auto_wrap_bambu_3mf(
            str(gcode), effective_printer_id=None, stl_path=None,
        )
        assert threemf is None
        assert warning is None


class TestWrapFailureIsNonFatal:
    def test_repackager_exception_returns_warning_not_crash(
        self, tmp_path: Path, monkeypatch
    ) -> None:
        gcode = _write_dummy_gcode(tmp_path)
        import kiln.printers.bambu_3mf as _b3mf
        def boom(*args, **kwargs):
            raise RuntimeError("simulated repackage failure")
        monkeypatch.setattr(_b3mf, "build_bambu_3mf", boom)

        threemf, warning = _auto_wrap_bambu_3mf(
            str(gcode), effective_printer_id="bambu_a1", stl_path=None,
        )
        assert threemf is None
        assert warning is not None
        assert "auto-wrap failed" in warning.lower()


class TestWrapOutputPathConvention:
    def test_output_named_gcode_3mf_alongside_source(
        self, tmp_path: Path, monkeypatch
    ) -> None:
        gcode = _write_dummy_gcode(tmp_path)
        import kiln.printers.bambu_3mf as _b3mf
        def ok(src, dst, **kwargs):
            Path(dst).write_bytes(b"PK")
        monkeypatch.setattr(_b3mf, "build_bambu_3mf", ok)

        threemf, _ = _auto_wrap_bambu_3mf(
            str(gcode), effective_printer_id="bambu_a1", stl_path=None,
        )
        assert threemf.endswith(".gcode.3mf")
        # Should live alongside the source gcode
        assert Path(threemf).parent == gcode.parent


class TestStlRoutedForThumbnailGeneration:
    """Regression: an .stl must go to stl_paths (OpenSCAD thumbnail
    fallback), NOT to source_3mf_path (which expects a .3mf zip and
    silently yields a blank printer-LCD preview)."""

    def test_stl_input_routes_to_stl_paths(
        self, tmp_path: Path, monkeypatch
    ) -> None:
        gcode = _write_dummy_gcode(tmp_path)
        stl = tmp_path / "disc.stl"
        stl.write_text("solid test\nendsolid\n")

        import kiln.printers.bambu_3mf as _b3mf
        captured: dict = {}

        def fake_build(body, dst, **kwargs):
            captured.update(kwargs)
            Path(dst).write_bytes(b"PK\x03\x04")

        monkeypatch.setattr(_b3mf, "build_bambu_3mf", fake_build)

        _auto_wrap_bambu_3mf(
            str(gcode), effective_printer_id="bambu_a1", stl_path=str(stl),
        )
        assert captured.get("stl_paths") == [str(stl)]
        assert captured.get("source_3mf_path") is None

    def test_3mf_input_routes_to_source_3mf_path(
        self, tmp_path: Path, monkeypatch
    ) -> None:
        gcode = _write_dummy_gcode(tmp_path)
        src_3mf = tmp_path / "source.3mf"
        src_3mf.write_bytes(b"PK\x03\x04")  # minimal zip header

        import kiln.printers.bambu_3mf as _b3mf
        captured: dict = {}

        def fake_build(body, dst, **kwargs):
            captured.update(kwargs)
            Path(dst).write_bytes(b"PK\x03\x04")

        monkeypatch.setattr(_b3mf, "build_bambu_3mf", fake_build)

        _auto_wrap_bambu_3mf(
            str(gcode), effective_printer_id="bambu_a1", stl_path=str(src_3mf),
        )
        assert captured.get("source_3mf_path") == str(src_3mf)
        assert captured.get("stl_paths") is None

    def test_unknown_extension_routes_to_neither(
        self, tmp_path: Path, monkeypatch
    ) -> None:
        gcode = _write_dummy_gcode(tmp_path)
        step = tmp_path / "part.step"
        step.write_text("ISO-10303-21;\n")

        import kiln.printers.bambu_3mf as _b3mf
        captured: dict = {}

        def fake_build(body, dst, **kwargs):
            captured.update(kwargs)
            Path(dst).write_bytes(b"PK\x03\x04")

        monkeypatch.setattr(_b3mf, "build_bambu_3mf", fake_build)

        _auto_wrap_bambu_3mf(
            str(gcode), effective_printer_id="bambu_a1", stl_path=str(step),
        )
        assert captured.get("stl_paths") is None
        assert captured.get("source_3mf_path") is None
