"""The pytest plugin: fixtures a console imports to write thin UI specs.

Registered as a ``pytest11`` entry point, so installing ``mgf-uitest-web`` makes
these fixtures available to a consumer's tests automatically (mirroring how
``mgf-common`` ships its plugin). The one thing a consumer must provide is a
``console_launch`` fixture returning a :class:`ConsoleLaunch` — everything else
(the browser, the responsive matrix, the running server, the ``console`` wrapper)
is supplied here.

Responsive matrix: ``viewport`` and ``color_scheme`` are indirectly
parametrizable, so a spec runs across widths/themes with
``@pytest.mark.parametrize("viewport", ["mobile", "tablet", "desktop"], indirect=True)``.
"""

from __future__ import annotations

import os
from pathlib import Path
from typing import TYPE_CHECKING, Literal, cast

import pytest

from .browser import launch_browser
from .checks import watch_health
from .console import Console, ConsoleLaunch, Viewport
from .server import free_port, live_server

if TYPE_CHECKING:
    from collections.abc import Iterator

    from playwright.sync_api import Browser, BrowserContext, Page, Playwright

    from .server import LiveServer

#: The default responsive matrix — phone / tablet / desktop.
VIEWPORTS: dict[str, Viewport] = {
    "mobile": Viewport("mobile", 390, 844),
    "tablet": Viewport("tablet", 834, 1112),
    "desktop": Viewport("desktop", 1280, 900),
}


# --------------------------------------------------------------------------- #
# what the console must provide                                               #
# --------------------------------------------------------------------------- #
@pytest.fixture
def console_port() -> int:
    """A free ephemeral port for this test's console. Consumers depend on this."""
    return free_port()


@pytest.fixture
def console_launch(console_port: int) -> ConsoleLaunch:
    """OVERRIDE ME: return a ConsoleLaunch describing how to start your console.

    Provide this in your own conftest, e.g.::

        @pytest.fixture
        def console_launch(tmp_path, console_port):
            return ConsoleLaunch(
                command=[MY_CLI, "--port", str(console_port)],
                env={"MY_STATE_ROOT": str(build_state(tmp_path))},
            )
    """
    raise NotImplementedError(
        "mgf-uitest-web needs a 'console_launch' fixture returning a ConsoleLaunch "
        "that describes how to start your console on the injected `console_port`."
    )


# --------------------------------------------------------------------------- #
# the running console (black-box subprocess)                                  #
# --------------------------------------------------------------------------- #
@pytest.fixture
def console_server(console_launch: ConsoleLaunch, console_port: int) -> Iterator[LiveServer]:
    """Spawn the console described by ``console_launch`` and yield it once serving."""
    env = {**os.environ, **dict(console_launch.env)}
    with live_server(
        console_launch.command,
        port=console_port,
        ready_path=console_launch.ready_path,
        env=env,
        cwd=console_launch.cwd,
        timeout=console_launch.timeout,
    ) as server:
        yield server


# --------------------------------------------------------------------------- #
# the browser (one per session) + responsive matrix                          #
# --------------------------------------------------------------------------- #
@pytest.fixture(scope="session")
def _ui_playwright() -> Iterator[Playwright]:
    from playwright.sync_api import sync_playwright

    with sync_playwright() as pw:
        yield pw


@pytest.fixture(scope="session")
def ui_browser(_ui_playwright: Playwright) -> Iterator[Browser]:
    """A single pinned bundled Chromium for the whole test session."""
    browser = launch_browser(_ui_playwright)
    yield browser
    browser.close()


@pytest.fixture
def viewport(request: pytest.FixtureRequest) -> Viewport:
    """The viewport for this test — a label from :data:`VIEWPORTS` or a Viewport.

    Parametrize indirectly to sweep the matrix; defaults to ``desktop``.
    """
    param = getattr(request, "param", "desktop")
    return VIEWPORTS[param] if isinstance(param, str) else param


@pytest.fixture
def color_scheme(request: pytest.FixtureRequest) -> str:
    """The colour scheme (``light`` / ``dark``); defaults to ``light``.

    Parametrize indirectly to also render dark mode.
    """
    return getattr(request, "param", "light")


@pytest.fixture
def ui_context(
    ui_browser: Browser, viewport: Viewport, color_scheme: str
) -> Iterator[BrowserContext]:
    """A fresh browser context at this test's viewport + colour scheme."""
    context = ui_browser.new_context(
        viewport=viewport.size,
        color_scheme=cast('Literal["dark", "light", "no-preference", "null"]', color_scheme),
    )
    yield context
    context.close()


@pytest.fixture
def ui_page(ui_context: BrowserContext) -> Iterator[Page]:
    """A fresh page for this test."""
    page = ui_context.new_page()
    page.set_default_timeout(10_000)
    yield page
    page.close()


# --------------------------------------------------------------------------- #
# output + the ergonomic wrapper                                             #
# --------------------------------------------------------------------------- #
@pytest.fixture(scope="session")
def gallery_dir(request: pytest.FixtureRequest) -> Path:
    """Where snapshots land — ``<rootdir>/tests/_artifacts/gallery`` by default."""
    root = Path(request.config.rootpath)
    directory = root / "tests" / "_artifacts" / "gallery"
    directory.mkdir(parents=True, exist_ok=True)
    return directory


@pytest.fixture
def console(
    console_server: LiveServer,
    ui_page: Page,
    viewport: Viewport,
    color_scheme: str,
    gallery_dir: Path,
) -> Console:
    """The console under test, ready to ``open()`` and assert against.

    Health listeners are attached now (before the spec navigates) so load-time
    console/network errors are captured.
    """
    health = watch_health(ui_page)
    return Console(
        ui_page,
        console_server,
        health,
        gallery_dir,
        viewport=viewport,
        theme=color_scheme,
    )
