# region TestScreenshotService [TYPE Module]
# @RELATION  BINDS_TO ->[ScreenshotService]
# @SEMANTICS: tests, screenshot-service, navigation, timeout-regression
# @PURPOSE: Protect dashboard screenshot navigation from brittle networkidle waits.

import pytest

from src.plugins.llm_analysis.service import ScreenshotService


# region test_iter_login_roots_includes_child_frames [TYPE Function]
# @RELATION  BINDS_TO ->[EXT:frontend:TestScreenshotService]
# @PURPOSE: Login discovery must search embedded auth frames, not only the main page.
# @PRE  Page exposes child frames list.
# @POST  Returned roots include page plus child frames in order.
def test_iter_login_roots_includes_child_frames():
    frame_a = object()
    frame_b = object()
    fake_page = type("FakePage", (), {"frames": [frame_a, frame_b]})()
    service = ScreenshotService(env=type("Env", (), {})())

    roots = service._iter_login_roots(fake_page)

    assert roots == [fake_page, frame_a, frame_b]


# endregion test_iter_login_roots_includes_child_frames


# region test_response_looks_like_login_page_detects_login_markup [TYPE Function]
# @RELATION  BINDS_TO ->[EXT:frontend:TestScreenshotService]
# @PURPOSE: Direct login fallback must reject responses that render the login screen again.
# @PRE  Response body contains stable login-page markers.
# @POST  Helper returns True so caller treats fallback as failed authentication.
def test_response_looks_like_login_page_detects_login_markup():
    service = ScreenshotService(env=type("Env", (), {})())

    result = service._response_looks_like_login_page(
        """
        <html>
          <body>
            <p>Enter your login and password below</p>
            <label>Username:</label>
            <label>Password:</label>
            <button>Sign in</button>
          </body>
        </html>
        """
    )

    assert result is True


# endregion test_response_looks_like_login_page_detects_login_markup


# region test_find_first_visible_locator_skips_hidden_first_match [TYPE Function]
# @RELATION  BINDS_TO ->[EXT:frontend:TestScreenshotService]
# @PURPOSE: Locator helper must not reject a selector collection just because its first element is hidden.
# @PRE  First matched element is hidden and second matched element is visible.
# @POST  Helper returns the second visible candidate.
@pytest.mark.anyio
async def test_find_first_visible_locator_skips_hidden_first_match():
    class _FakeElement:
        def __init__(self, visible, label):
            self._visible = visible
            self.label = label

        async def is_visible(self):
            return self._visible

    class _FakeLocator:
        def __init__(self, elements):
            self._elements = elements

        async def count(self):
            return len(self._elements)

        def nth(self, index):
            return self._elements[index]

    service = ScreenshotService(env=type("Env", (), {})())
    hidden_then_visible = _FakeLocator(
        [
            _FakeElement(False, "hidden"),
            _FakeElement(True, "visible"),
        ]
    )

    result = await service._find_first_visible_locator([hidden_then_visible])

    assert result.label == "visible"


# endregion test_find_first_visible_locator_skips_hidden_first_match


# region test_submit_login_via_form_post_uses_browser_context_request [TYPE Function]
# @RELATION  BINDS_TO ->[EXT:frontend:TestScreenshotService]
# @PURPOSE: Fallback login must submit hidden fields and credentials through the context request cookie jar.
# @PRE  Login DOM exposes csrf hidden field and request context returns authenticated HTML.
# @POST  Helper returns True and request payload contains csrf_token plus credentials plus request options.
@pytest.mark.anyio
async def test_submit_login_via_form_post_uses_browser_context_request():
    class _FakeInput:
        def __init__(self, name, value):
            self._name = name
            self._value = value

        async def get_attribute(self, attr_name):
            return self._name if attr_name == "name" else None

        async def input_value(self):
            return self._value

    class _FakeLocator:
        def __init__(self, items):
            self._items = items

        async def count(self):
            return len(self._items)

        def nth(self, index):
            return self._items[index]

    class _FakeResponse:
        status = 200
        url = "https://example.test/welcome/"

        async def text(self):
            return "<html><body>Welcome</body></html>"

    class _FakeRequest:
        def __init__(self):
            self.calls = []

        async def post(
            self,
            url,
            form=None,
            headers=None,
            timeout=None,
            fail_on_status_code=None,
            max_redirects=None,
        ):
            self.calls.append(
                {
                    "url": url,
                    "form": dict(form or {}),
                    "headers": dict(headers or {}),
                    "timeout": timeout,
                    "fail_on_status_code": fail_on_status_code,
                    "max_redirects": max_redirects,
                }
            )
            return _FakeResponse()

    class _FakeContext:
        def __init__(self):
            self.request = _FakeRequest()

    class _FakePage:
        def __init__(self):
            self.frames = []
            self.context = _FakeContext()

        def locator(self, selector):
            if selector == "input[type='hidden'][name]":
                return _FakeLocator(
                    [
                        _FakeInput("csrf_token", "csrf-123"),
                        _FakeInput("next", "/superset/welcome/"),
                    ]
                )
            return _FakeLocator([])

    env = type("Env", (), {"username": "admin", "password": "secret"})()
    service = ScreenshotService(env=env)
    page = _FakePage()

    result = await service._submit_login_via_form_post(
        page, "https://example.test/login/"
    )

    assert result is True
    assert page.context.request.calls == [
        {
            "url": "https://example.test/login/",
            "form": {
                "csrf_token": "csrf-123",
                "next": "/superset/welcome/",
                "username": "admin",
                "password": "secret",
            },
            "headers": {
                "Origin": "https://example.test",
                "Referer": "https://example.test/login/",
            },
            "timeout": 10000,
            "fail_on_status_code": False,
            "max_redirects": 0,
        }
    ]


# endregion test_submit_login_via_form_post_uses_browser_context_request


# region test_submit_login_via_form_post_accepts_authenticated_redirect [TYPE Function]
# @RELATION  BINDS_TO ->[EXT:frontend:TestScreenshotService]
# @PURPOSE: Fallback login must treat non-login 302 redirect as success without waiting for redirect target.
# @PRE  Request response is 302 with Location outside login path.
# @POST  Helper returns True.
@pytest.mark.anyio
async def test_submit_login_via_form_post_accepts_authenticated_redirect():
    class _FakeInput:
        def __init__(self, name, value):
            self._name = name
            self._value = value

        async def get_attribute(self, attr_name):
            return self._name if attr_name == "name" else None

        async def input_value(self):
            return self._value

    class _FakeLocator:
        def __init__(self, items):
            self._items = items

        async def count(self):
            return len(self._items)

        def nth(self, index):
            return self._items[index]

    class _FakeResponse:
        status = 302
        url = "https://example.test/login/"
        headers = {"location": "/superset/welcome/"}

        async def text(self):
            return ""

    class _FakeRequest:
        async def post(
            self,
            url,
            form=None,
            headers=None,
            timeout=None,
            fail_on_status_code=None,
            max_redirects=None,
        ):
            return _FakeResponse()

    class _FakeContext:
        def __init__(self):
            self.request = _FakeRequest()

    class _FakePage:
        def __init__(self):
            self.frames = []
            self.context = _FakeContext()

        def locator(self, selector):
            if selector == "input[type='hidden'][name]":
                return _FakeLocator([_FakeInput("csrf_token", "csrf-123")])
            return _FakeLocator([])

    env = type("Env", (), {"username": "admin", "password": "secret"})()
    service = ScreenshotService(env=env)

    result = await service._submit_login_via_form_post(
        _FakePage(), "https://example.test/login/"
    )

    assert result is True


# endregion test_submit_login_via_form_post_accepts_authenticated_redirect


# region test_submit_login_via_form_post_rejects_login_markup_response [TYPE Function]
# @RELATION  BINDS_TO ->[EXT:frontend:TestScreenshotService]
# @PURPOSE: Fallback login must fail when POST response still contains login form content.
# @PRE  Login DOM exposes csrf hidden field and request response renders login markup.
# @POST  Helper returns False.
@pytest.mark.anyio
async def test_submit_login_via_form_post_rejects_login_markup_response():
    class _FakeInput:
        def __init__(self, name, value):
            self._name = name
            self._value = value

        async def get_attribute(self, attr_name):
            return self._name if attr_name == "name" else None

        async def input_value(self):
            return self._value

    class _FakeLocator:
        def __init__(self, items):
            self._items = items

        async def count(self):
            return len(self._items)

        def nth(self, index):
            return self._items[index]

    class _FakeResponse:
        status = 200
        url = "https://example.test/login/"

        async def text(self):
            return """
            <html>
              <body>
                Enter your login and password below
                Username:
                Password:
                Sign in
              </body>
            </html>
            """

    class _FakeRequest:
        async def post(
            self,
            url,
            form=None,
            headers=None,
            timeout=None,
            fail_on_status_code=None,
            max_redirects=None,
        ):
            return _FakeResponse()

    class _FakeContext:
        def __init__(self):
            self.request = _FakeRequest()

    class _FakePage:
        def __init__(self):
            self.frames = []
            self.context = _FakeContext()

        def locator(self, selector):
            if selector == "input[type='hidden'][name]":
                return _FakeLocator([_FakeInput("csrf_token", "csrf-123")])
            return _FakeLocator([])

    env = type("Env", (), {"username": "admin", "password": "secret"})()
    service = ScreenshotService(env=env)

    result = await service._submit_login_via_form_post(
        _FakePage(), "https://example.test/login/"
    )

    assert result is False


# endregion test_submit_login_via_form_post_rejects_login_markup_response


# region test_goto_resilient_falls_back_from_domcontentloaded_to_load [TYPE Function]
# @RELATION  BINDS_TO ->[EXT:frontend:TestScreenshotService]
# @PURPOSE: Pages with unstable primary wait must retry with fallback wait strategy.
# @PRE  First page.goto call raises; second succeeds.
# @POST  Helper returns second response and attempts both wait modes in order.
@pytest.mark.anyio
async def test_goto_resilient_falls_back_from_domcontentloaded_to_load():
    class _FakePage:
        def __init__(self):
            self.calls = []

        async def goto(self, url, wait_until, timeout):
            self.calls.append((url, wait_until, timeout))
            if wait_until == "domcontentloaded":
                raise RuntimeError("primary wait failed")
            return {"ok": True, "url": url, "wait_until": wait_until}

    page = _FakePage()
    service = ScreenshotService(env=type("Env", (), {})())

    response = await service._goto_resilient(
        page,
        "https://example.test/dashboard",
        primary_wait_until="domcontentloaded",
        fallback_wait_until="load",
        timeout=1234,
    )

    assert response["ok"] is True
    assert page.calls == [
        ("https://example.test/dashboard", "domcontentloaded", 1234),
        ("https://example.test/dashboard", "load", 1234),
    ]


# endregion test_goto_resilient_falls_back_from_domcontentloaded_to_load
# endregion TestScreenshotService
