"""Tests for scripts/repush_transform.py.

All tests mock Canvas API calls (token-less). Run with:
    pytest tests/test_repush_transform.py -q
"""

import pathlib
import sys
import types
from unittest.mock import MagicMock, patch

ROOT = pathlib.Path(__file__).resolve().parents[1]
SCRIPTS = ROOT / "scripts"
sys.path.insert(0, str(SCRIPTS))


def _import_repush():
    """Import repush_transform with canvas_api and staging_manager stubbed."""
    # Provide minimal stubs so repush_transform can be imported.
    stub_canvas = types.ModuleType("canvas_api")
    stub_canvas.get_config = MagicMock(return_value={
        "course_id": "99999",
        "course_url": "https://canvas.asu.edu/api/v1/courses/99999",
        "headers": {"Authorization": "Bearer FAKE"},
        "domain": "canvas.asu.edu",
    })
    stub_canvas.get_page = MagicMock()
    sys.modules.setdefault("canvas_api", stub_canvas)

    stub_staging = types.ModuleType("staging_manager")
    stub_staging.stage = MagicMock(return_value={"ok": True})
    sys.modules.setdefault("staging_manager", stub_staging)

    stub_push = types.ModuleType("push_to_canvas")
    stub_push.push_page = MagicMock(return_value={"ok": True, "canvas_url": "https://fake"})
    sys.modules.setdefault("push_to_canvas", stub_push)

    import importlib
    if "repush_transform" in sys.modules:
        del sys.modules["repush_transform"]
    import repush_transform
    return repush_transform, stub_canvas, stub_staging, stub_push


class TestRepushTransform:
    # Modules this class installs as stubs — must be cleaned up after every test.
    _STUB_MODULES = ["canvas_api", "staging_manager", "push_to_canvas", "repush_transform"]
    # Class-level snapshot taken once before any test in this class runs.
    _class_saved: dict = {}

    @classmethod
    def setup_class(cls):
        """Snapshot sys.modules for all stub keys before any test in this class."""
        cls._class_saved = {m: sys.modules.get(m) for m in cls._STUB_MODULES}

    @classmethod
    def teardown_class(cls):
        """Restore sys.modules exactly to the pre-class state."""
        for m in cls._STUB_MODULES:
            sys.modules.pop(m, None)
        for m, original in cls._class_saved.items():
            if original is not None:
                sys.modules[m] = original

    def setup_method(self):
        """Remove any stubs left from previous test so each test starts clean."""
        for m in self._STUB_MODULES:
            sys.modules.pop(m, None)

    def teardown_method(self):
        """Remove stubs installed by this test (class teardown handles full restore)."""
        for m in self._STUB_MODULES:
            sys.modules.pop(m, None)

    def _make_stubs(self, live_body):
        stub_canvas = types.ModuleType("canvas_api")
        stub_canvas.get_config = MagicMock(return_value={
            "course_id": "99999",
            "course_url": "https://canvas.asu.edu/api/v1/courses/99999",
            "headers": {},
            "domain": "canvas.asu.edu",
        })
        stub_canvas.get_page = MagicMock(return_value={"body": live_body})
        sys.modules["canvas_api"] = stub_canvas

        stub_staging = types.ModuleType("staging_manager")
        stub_staging.stage = MagicMock(return_value={"ok": True})
        sys.modules["staging_manager"] = stub_staging

        stub_push = types.ModuleType("push_to_canvas")
        stub_push.push_page = MagicMock(return_value={"ok": True, "canvas_url": "https://fake"})
        sys.modules["push_to_canvas"] = stub_push

        return stub_canvas, stub_staging, stub_push

    def test_transform_applied_to_live_body(self):
        live_body = "<p>The result—was success.</p>"
        stub_canvas, stub_staging, stub_push = self._make_stubs(live_body)

        import repush_transform
        rc = repush_transform.run(
            course_id="99999",
            object_type="page",
            slugs=["m1-lesson"],
            transform_name="dedash",
            confirm_verb="push",
            dry_run=False,
        )
        assert rc == 0
        # staging was called with transformed body (no em dash).
        assert stub_staging.stage.called
        staged_content = stub_staging.stage.call_args[0][3]  # 4th positional arg = content
        assert "—" not in staged_content

    def test_unchanged_pages_skipped(self):
        # Body has no em/en dashes; transform should skip.
        live_body = "<p>No dashes here.</p>"
        stub_canvas, stub_staging, stub_push = self._make_stubs(live_body)

        import repush_transform
        rc = repush_transform.run(
            course_id="99999",
            object_type="page",
            slugs=["m1-lesson"],
            transform_name="dedash",
            confirm_verb="push",
            dry_run=False,
        )
        assert rc == 0
        assert not stub_staging.stage.called
        assert not stub_push.push_page.called

    def test_dry_run_no_writes(self):
        live_body = "<p>A—B</p>"
        stub_canvas, stub_staging, stub_push = self._make_stubs(live_body)

        import repush_transform
        rc = repush_transform.run(
            course_id="99999",
            object_type="page",
            slugs=["m1-lesson"],
            transform_name="dedash",
            dry_run=True,
        )
        assert rc == 0
        assert not stub_staging.stage.called
        assert not stub_push.push_page.called

    def test_missing_confirm_verb_exits_one(self):
        live_body = "<p>A—B</p>"
        self._make_stubs(live_body)

        import repush_transform
        rc = repush_transform.run(
            course_id="99999",
            object_type="page",
            slugs=["m1-lesson"],
            transform_name="dedash",
            confirm_verb=None,
            dry_run=False,
        )
        assert rc == 1

    def test_ambiguous_confirm_verb_rejected(self):
        live_body = "<p>A—B</p>"
        self._make_stubs(live_body)

        import repush_transform
        for bad_verb in ("yes", "ok", "sure", "confirm"):
            self.setup_method()
            self._make_stubs(live_body)
            import repush_transform as rt
            rc = rt.run(
                course_id="99999",
                object_type="page",
                slugs=["m1-lesson"],
                transform_name="dedash",
                confirm_verb=bad_verb,
                dry_run=False,
            )
            assert rc == 1, f"verb '{bad_verb}' should be rejected"
            self.setup_method()

    def test_canvas_api_error_returns_one(self):
        stub_canvas = types.ModuleType("canvas_api")
        stub_canvas.get_config = MagicMock(return_value={
            "course_id": "99999", "course_url": "", "headers": {}, "domain": "canvas.asu.edu",
        })
        stub_canvas.get_page = MagicMock(return_value=None)  # simulate failure
        sys.modules["canvas_api"] = stub_canvas
        sys.modules["staging_manager"] = types.ModuleType("staging_manager")
        sys.modules["push_to_canvas"] = types.ModuleType("push_to_canvas")

        import repush_transform
        rc = repush_transform.run(
            course_id="99999",
            object_type="page",
            slugs=["missing-slug"],
            transform_name="dedash",
            confirm_verb="push",
            dry_run=False,
        )
        assert rc == 1

    def test_invalid_transform_exits_one(self):
        self._make_stubs("<p>body</p>")
        import repush_transform
        rc = repush_transform.run(
            course_id="99999",
            object_type="page",
            slugs=["m1-lesson"],
            transform_name="nonexistent",
            confirm_verb="push",
            dry_run=False,
        )
        assert rc == 1
