"""get_rendered_content must hand back anchor text alongside each href --
the crawl-prioritization fallback classifies links by their text, not just
their URL shape.

Note on trimming: the actual `.trim()` happens inside the JS source string
passed to `eval_on_selector_all` (see browser.py), which this test mocks
wholesale. A Python-side unit test cannot exercise that JS engine, so it
cannot verify the trimming behavior itself -- it can only verify that
get_rendered_content passes eval_on_selector_all's return value through
unchanged (Python does no trimming of its own). We still mock the return
value with untrimmed text (leading/trailing whitespace) to match what a
real DOM's textContent would look like before the JS-side trim runs, so a
reader comparing this test to the brief isn't misled into thinking the
mock already reflects trimmed output."""
import pytest
from unittest.mock import AsyncMock, MagicMock

from app.services.content.browser import BrowserService


@pytest.mark.asyncio
async def test_links_carry_their_anchor_text():
    service = BrowserService()
    fake_page = AsyncMock()
    fake_page.goto = AsyncMock()
    fake_page.content = AsyncMock(return_value="<html></html>")
    fake_page.eval_on_selector_all = AsyncMock(return_value=[
        {"href": "https://shop.example.com/products/oxford-shirt", "text": " Oxford Shirt "},
        {"href": "https://shop.example.com/collections/all", "text": " Shop All "},
    ])
    fake_page.title = AsyncMock(return_value="Home")
    fake_page.close = AsyncMock()

    fake_context = AsyncMock()
    fake_context.new_page = AsyncMock(return_value=fake_page)
    fake_context.close = AsyncMock()

    service._browser = MagicMock()
    service._browser.new_context = AsyncMock(return_value=fake_context)

    content, links, meta = await service.get_rendered_content("https://shop.example.com/")

    # get_rendered_content must pass eval_on_selector_all's dicts through
    # unchanged -- it does no trimming of its own (the trim is baked into
    # the JS source string and runs in the browser, not here).
    assert links == [
        {"href": "https://shop.example.com/products/oxford-shirt", "text": " Oxford Shirt "},
        {"href": "https://shop.example.com/collections/all", "text": " Shop All "},
    ]


@pytest.mark.asyncio
async def test_a_disconnected_browser_is_relaunched_before_use():
    """The underlying Chromium process can die mid-session (observed in
    production as `Browser.new_context: ... the handler is closed`). Once
    that happens the shared singleton must self-heal on the next call
    instead of failing every crawl until someone restarts the service."""
    service = BrowserService()

    dead_browser = MagicMock()
    dead_browser.is_connected = MagicMock(return_value=False)
    service._browser = dead_browser

    fake_page = AsyncMock()
    fake_page.goto = AsyncMock()
    fake_page.content = AsyncMock(return_value="<html></html>")
    fake_page.eval_on_selector_all = AsyncMock(return_value=[])
    fake_page.title = AsyncMock(return_value="Home")
    fake_page.close = AsyncMock()

    fake_context = AsyncMock()
    fake_context.new_page = AsyncMock(return_value=fake_page)
    fake_context.close = AsyncMock()

    live_browser = MagicMock()
    live_browser.is_connected = MagicMock(return_value=True)
    live_browser.new_context = AsyncMock(return_value=fake_context)

    async def fake_init_browser():
        service._browser = live_browser

    service._init_browser = AsyncMock(side_effect=fake_init_browser)

    await service.get_rendered_content("https://shop.example.com/")

    service._init_browser.assert_awaited_once()
    live_browser.new_context.assert_awaited_once()


@pytest.mark.asyncio
async def test_a_stale_is_connected_flag_still_recovers_on_new_context_failure():
    """Observed in production: is_connected() can still report True for a
    beat after Chromium has actually died, so new_context() itself throws
    (`Browser.new_context: ... the handler is closed`). The pre-check alone
    is not enough -- the failure must be caught and retried after a
    relaunch, or every crawl fails until someone restarts the service."""
    service = BrowserService()

    stale_browser = MagicMock()
    stale_browser.is_connected = MagicMock(return_value=True)
    stale_browser.new_context = AsyncMock(
        side_effect=RuntimeError("Browser.new_context: ... the handler is closed"))
    service._browser = stale_browser

    fake_page = AsyncMock()
    fake_page.goto = AsyncMock()
    fake_page.content = AsyncMock(return_value="<html></html>")
    fake_page.eval_on_selector_all = AsyncMock(return_value=[])
    fake_page.title = AsyncMock(return_value="Home")
    fake_page.close = AsyncMock()

    fake_context = AsyncMock()
    fake_context.new_page = AsyncMock(return_value=fake_page)
    fake_context.close = AsyncMock()

    live_browser = MagicMock()
    live_browser.is_connected = MagicMock(return_value=True)
    live_browser.new_context = AsyncMock(return_value=fake_context)

    async def fake_init_browser():
        service._browser = live_browser

    service._init_browser = AsyncMock(side_effect=fake_init_browser)

    content, links, meta = await service.get_rendered_content("https://shop.example.com/")

    service._init_browser.assert_awaited_once()
    live_browser.new_context.assert_awaited_once()
    assert content == "<html></html>"


@pytest.mark.asyncio
async def test_a_healthy_browser_is_not_relaunched():
    service = BrowserService()

    fake_page = AsyncMock()
    fake_page.goto = AsyncMock()
    fake_page.content = AsyncMock(return_value="<html></html>")
    fake_page.eval_on_selector_all = AsyncMock(return_value=[])
    fake_page.title = AsyncMock(return_value="Home")
    fake_page.close = AsyncMock()

    fake_context = AsyncMock()
    fake_context.new_page = AsyncMock(return_value=fake_page)
    fake_context.close = AsyncMock()

    live_browser = MagicMock()
    live_browser.is_connected = MagicMock(return_value=True)
    live_browser.new_context = AsyncMock(return_value=fake_context)
    service._browser = live_browser
    service._init_browser = AsyncMock()

    await service.get_rendered_content("https://shop.example.com/")

    service._init_browser.assert_not_awaited()
