"""Installing as an app, and the composer's single send/stop button."""

from __future__ import annotations

from pathlib import Path

from fastapi.testclient import TestClient

from lembas.services import settings_store
from lembas.web.templating import STATIC_DIR


# --- Manifest ----------------------------------------------------------------
def test_the_manifest_is_readable_when_signed_out(client: TestClient):
    """A browser fetches the manifest outside any page's session."""
    response = client.get("/manifest.webmanifest")
    assert response.status_code == 200
    assert response.headers["content-type"].startswith("application/manifest+json")


def test_the_manifest_carries_the_instance_name(client: TestClient, db, registered):
    """The name moved to /admin/customization with the rest of the identity."""
    client.post(
        "/admin/customization/identity",
        data={"instance_name": "Rivendell", "tagline": ""},
        follow_redirects=False,
    )
    assert client.get("/manifest.webmanifest").json()["name"] == "Rivendell"


def test_the_manifest_offers_a_maskable_icon(client: TestClient):
    """Without one, Android crops the corners off the wafer."""
    icons = client.get("/manifest.webmanifest").json()["icons"]
    assert any(icon["purpose"] == "maskable" for icon in icons)
    assert any(icon["sizes"] == "512x512" and icon["purpose"] == "any" for icon in icons)


def test_every_manifest_icon_exists(client: TestClient):
    for icon in client.get("/manifest.webmanifest").json()["icons"]:
        assert client.get(icon["src"]).status_code == 200, icon["src"]


def test_the_manifest_starts_at_the_chat(client: TestClient):
    payload = client.get("/manifest.webmanifest").json()
    assert payload["start_url"] == "/chat"
    assert payload["scope"] == "/"
    assert payload["display"] == "standalone"


# --- Service worker ----------------------------------------------------------
def test_the_worker_is_served_from_the_root(client: TestClient):
    """A worker under /static/js/ would have scope /static/js/ and control
    nothing."""
    response = client.get("/sw.js")
    assert response.status_code == 200
    assert response.headers["content-type"].startswith("text/javascript")


def test_the_worker_is_never_cached(client: TestClient):
    """A stale worker keeps serving a stale cache."""
    assert "no-store" in client.get("/sw.js").headers["cache-control"]


def test_the_worker_leaves_the_api_alone():
    """The reply stream, the unread poll and attachment downloads all live
    under /api/. A cached response on any of them is at best stale."""
    source = (STATIC_DIR / "js" / "sw.js").read_text()
    assert '"/api/"' in source
    assert "text/event-stream" in source


def test_every_precached_asset_exists(client: TestClient):
    """addAll is all-or-nothing in most implementations, and a missing entry is
    invisible until someone opens the developer tools."""
    source = (STATIC_DIR / "js" / "sw.js").read_text()
    shell = source.split("var SHELL = [", 1)[1].split("];", 1)[0]
    paths = [line.strip().strip('",') for line in shell.splitlines() if '"' in line]

    assert paths
    for path in paths:
        assert client.get(path).status_code == 200, path


def test_the_offline_page_stands_on_its_own(client: TestClient):
    """Cached at install time, so it must render with no user and no chats."""
    response = client.get("/offline")
    assert response.status_code == 200
    assert 'class="sidebar' not in response.text
    assert 'id="thread' not in response.text


def test_the_page_links_the_manifest_and_the_apple_icon(client: TestClient, registered):
    page = client.get("/chat").text
    assert 'rel="manifest"' in page
    assert 'rel="apple-touch-icon"' in page
    assert 'name="theme-color"' in page


# --- Send and Stop -----------------------------------------------------------
def test_the_hidden_attribute_wins_over_component_styles():
    """`.btn` is display: inline-flex, which beats the browser's own
    `[hidden] { display: none }`. Without this rule a button hidden from
    JavaScript stays on screen -- which is how Stop came to sit permanently
    beside Send."""
    css = (STATIC_DIR / "css" / "app.css").read_text()
    assert "[hidden]" in css
    assert "display: none !important" in css


def test_the_composer_has_exactly_one_send_button(client: TestClient, db, registered):
    """One button that becomes Stop, not two that take turns being hidden."""
    _add_a_model(db)
    page = client.get("/chat").text
    assert page.count("data-composer-action") == 1


def test_the_send_button_carries_both_icons(client: TestClient, db, registered):
    """Rendered together and chosen in CSS, so the swap costs no layout and
    cannot flash an empty button."""
    _add_a_model(db)
    page = client.get("/chat").text
    assert "composer__icon--send" in page
    assert "composer__icon--stop" in page


def test_the_composer_starts_in_the_send_state(client: TestClient, db, registered):
    _add_a_model(db)
    assert 'data-composer-action="send"' in client.get("/chat").text


def _add_a_model(db):
    from lembas.db.models import Connection, Model

    connection = Connection(name="c", base_url="http://127.0.0.1:1", api_key_encrypted="")
    db.add(connection)
    db.commit()
    db.add(Model(connection_id=connection.id, model_id="m"))
    db.commit()


# --- The icons themselves ----------------------------------------------------
def test_the_generated_icons_are_committed():
    """They come from scripts/build_artwork.py and are committed like the SVGs;
    the running application has no rasteriser."""
    for name in (
        "icon-192.png",
        "icon-512.png",
        "icon-maskable-512.png",
        "apple-touch-icon-180.png",
    ):
        path = Path(STATIC_DIR) / "img" / name
        assert path.exists(), name
        assert path.read_bytes()[:8] == b"\x89PNG\r\n\x1a\n", name


def test_the_mic_appears_only_when_dictation_is_configured(
    client: TestClient, db, registered
):
    _add_a_model(db)
    assert "data-mic" not in client.get("/chat").text

    settings_store.update(
        db,
        {"stt_enabled": True, "stt_base_url": "http://stt"},
        key=settings_store.AUDIO,
    )
    assert "data-mic" in client.get("/chat").text
