import unittest
from unittest.mock import MagicMock

from abstra_internals.controllers.main import (
    EDITOR_PAGE_NAV_TIMEOUT_MS,
    MainController,
    _run_page_snapshot_warnings,
)
from abstra_internals.settings import Settings

Settings.set_root_path("/tmp")


def _make_controller(text_content="Treasury Dashboard", network_idle=True):
    """MainController mock with the real run_page/browser_open_page methods
    bound, and _browser_call stubbed to simulate the browser thread."""
    controller = MagicMock()
    controller.run_page = MainController.run_page.__get__(controller)
    controller.browser_open_page = MainController.browser_open_page.__get__(controller)
    controller._collect_page_state = MainController._collect_page_state.__get__(
        controller
    )

    page = MagicMock()
    page.id = "page-1"
    controller.get_page_stage = MagicMock(return_value=page)
    controller._get_browser_base_url = MagicMock(return_value="http://localhost:8080")

    calls = []

    def browser_call(method, *args, **kwargs):
        calls.append((method, args, kwargs))
        if method == "navigate_to_url":
            return {"tab_id": "tab-1", "url": args[0], "title": "title"}
        if method == "wait_for_network_idle":
            return network_idle
        if method == "get_text":
            return text_content
        if method in ("get_page_summary", "get_console_logs", "get_network_requests"):
            return []
        return None

    controller._browser_call = MagicMock(side_effect=browser_call)
    return controller, calls


class TestRunPageSnapshotWaits(unittest.TestCase):
    def test_navigates_with_tolerant_wait(self):
        """The editor /run URL can take longer than Playwright's 30s default
        to return its first byte (worker cold start + __render__)."""
        controller, calls = _make_controller()

        controller.run_page("page-1")

        nav_calls = [c for c in calls if c[0] == "navigate_to_url"]
        self.assertEqual(len(nav_calls), 1)
        _, _, kwargs = nav_calls[0]
        self.assertEqual(kwargs.get("wait_until"), "domcontentloaded")
        self.assertEqual(kwargs.get("timeout_ms"), EDITOR_PAGE_NAV_TIMEOUT_MS)

    def test_waits_for_network_idle_before_collecting(self):
        """Pages load data via async POSTs after render; snapshotting before
        those settle produces a blank-but-\"healthy\" snapshot."""
        controller, calls = _make_controller()

        controller.run_page("page-1")

        methods = [c[0] for c in calls]
        self.assertIn("wait_for_network_idle", methods)
        self.assertLess(
            methods.index("wait_for_network_idle"), methods.index("get_text")
        )

    def test_tab_closed_even_when_collect_fails(self):
        controller, calls = _make_controller()

        def failing_call(method, *args, **kwargs):
            calls.append((method, args, kwargs))
            if method == "navigate_to_url":
                return {"tab_id": "tab-1", "url": args[0], "title": "title"}
            if method == "get_page_summary":
                raise RuntimeError("browser crashed")
            return None

        controller._browser_call = MagicMock(side_effect=failing_call)

        with self.assertRaises(RuntimeError):
            controller.run_page("page-1")
        self.assertIn("close_page", [c[0] for c in calls])


class TestRunPageSnapshotWarnings(unittest.TestCase):
    def test_healthy_snapshot_has_no_warnings(self):
        controller, _ = _make_controller(text_content="data loaded", network_idle=True)

        result = controller.run_page("page-1")

        self.assertNotIn("warnings", result)

    def test_blank_render_is_flagged(self):
        controller, _ = _make_controller(text_content="  \n ", network_idle=True)

        result = controller.run_page("page-1")

        self.assertEqual(len(result["warnings"]), 1)
        self.assertIn("no visible text", result["warnings"][0])

    def test_pending_network_is_flagged(self):
        controller, _ = _make_controller(text_content="loading...", network_idle=False)

        result = controller.run_page("page-1")

        self.assertEqual(len(result["warnings"]), 1)
        self.assertIn("still in flight", result["warnings"][0])

    def test_blank_and_busy_produce_both_warnings(self):
        self.assertEqual(len(_run_page_snapshot_warnings("", network_idle=False)), 2)

    def test_helper_returns_empty_list_when_healthy(self):
        self.assertEqual(_run_page_snapshot_warnings("ok", network_idle=True), [])


class TestBrowserOpenPageNavigation(unittest.TestCase):
    def test_uses_tolerant_wait(self):
        controller, calls = _make_controller()

        controller.browser_open_page("page-1")

        nav_calls = [c for c in calls if c[0] == "navigate_to_url"]
        self.assertEqual(len(nav_calls), 1)
        _, _, kwargs = nav_calls[0]
        self.assertEqual(kwargs.get("wait_until"), "domcontentloaded")
        self.assertEqual(kwargs.get("timeout_ms"), EDITOR_PAGE_NAV_TIMEOUT_MS)


if __name__ == "__main__":
    unittest.main()
