"""One run against a real server on a real socket.

Every other HTTP test in this suite goes through `TestClient`, which is an
in-process ASGI call: no socket, no uvicorn, no HTTP parsing. That covers the
application and covers nothing about the thing `deploy/install.sh` actually
starts. Uvicorn's own behaviour -- how it frames a streaming response, whether
it holds a connection open, what it does on shutdown with a stream still
running -- is what a deployment depends on and what nothing here touched.

Deliberately **one** test file and a handful of assertions. This is a smoke
test: it is here so that "the server starts and serves" is a fact rather than an
inference, not to re-test the application through a slower transport.

Marked `slow` because it binds a port and waits for a process.
"""

from __future__ import annotations

import socket
import subprocess
import sys
import time

import httpx
import pytest

pytestmark = pytest.mark.slow


def _free_port() -> int:
    with socket.socket() as probe:
        probe.bind(("127.0.0.1", 0))
        return probe.getsockname()[1]


@pytest.fixture
def server(tmp_path):
    """A real `lembas serve`, on a real port, with its own data directory.

    Started as a subprocess rather than a thread: the point is the process the
    unit file starts, and an in-thread uvicorn shares this interpreter's already
    imported settings singleton.
    """
    port = _free_port()
    env = {
        "PATH": "/usr/bin:/bin",
        "HOME": str(tmp_path),
        "LEMBAS_DATA_DIR": str(tmp_path),
        "LEMBAS_HOST": "127.0.0.1",
        "LEMBAS_PORT": str(port),
        "LEMBAS_SECRET_KEY": "t" * 44,
        "LEMBAS_ALLOW_SIGNUP": "true",
    }
    process = subprocess.Popen(
        [sys.executable, "-m", "lembas.cli", "serve"],
        env=env,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        text=True,
    )
    base = f"http://127.0.0.1:{port}"
    try:
        deadline = time.monotonic() + 30
        while time.monotonic() < deadline:
            if process.poll() is not None:
                raise AssertionError(f"the server exited: {process.stdout.read()}")
            try:
                httpx.get(f"{base}/healthz", timeout=1.0)
                break
            except httpx.TransportError:
                time.sleep(0.2)
        else:  # pragma: no cover - only on a machine that cannot start it
            raise AssertionError("the server never answered")
        yield base
    finally:
        process.terminate()
        try:
            process.wait(timeout=10)
        except subprocess.TimeoutExpired:  # pragma: no cover
            process.kill()


def test_it_starts_and_answers(server):
    """`lembas serve` is what the systemd unit runs, and until now nothing
    checked that the command in the unit file works at all."""
    response = httpx.get(f"{server}/healthz", timeout=10.0)

    assert response.status_code == 200
    assert response.json()["status"] == "ok"


def test_a_signed_out_visitor_is_sent_to_the_sign_in_page(server):
    """Over real HTTP, with a real redirect, because the redirect is a header
    and headers are the part `TestClient` does not have to get right."""
    response = httpx.get(f"{server}/", follow_redirects=False, timeout=10.0)

    assert response.status_code in (302, 303, 307)
    assert "/auth/login" in response.headers["location"]


def test_the_stylesheets_it_serves_are_the_ones_in_the_tree(server):
    """The static mount, which is configuration rather than code and therefore
    fails at deploy time rather than in a unit test."""
    response = httpx.get(f"{server}/static/css/app.css", timeout=10.0)

    assert response.status_code == 200
    assert response.headers["content-type"].startswith("text/css")
    assert ".sidebar" in response.text


def test_a_fresh_instance_offers_a_way_in(server):
    """The first thing a new deployment does. On an empty database `/auth/login`
    redirects to registration, because an instance with no accounts and a sign-in
    form is a door with no key -- and the first account made becomes the
    administrator.

    Worth having over real HTTP: this is the exact path somebody walks thirty
    seconds after `install.sh` finishes, and it is a chain of redirects, which is
    the part that is headers rather than code.
    """
    response = httpx.get(f"{server}/auth/login", follow_redirects=True, timeout=10.0)

    assert response.status_code == 200
    assert "text/html" in response.headers["content-type"]
    assert "register" in str(response.url) or "register" in response.text.lower()


def test_an_account_can_be_created_and_used_over_real_http(server):
    """Registration, the session cookie, and a page that needs it -- end to end
    through uvicorn. A cookie is `Set-Cookie` plus the browser sending it back,
    and both halves are transport rather than application: `TestClient` has its
    own cookie jar and would pass whatever the header said.
    """
    with httpx.Client(base_url=server, timeout=15.0, follow_redirects=True) as client:
        made = client.post(
            "/auth/register",
            data={
                "name": "Frodo",
                "email": "f@shire.test",
                "password": "speak-friend-and-enter",
                "confirm": "speak-friend-and-enter",
            },
        )
        assert made.status_code == 200, made.text[:400]
        assert client.cookies, "no session cookie came back"

        # And the cookie actually admits us to something that requires one.
        chat = client.get("/chat")
        assert chat.status_code == 200
        assert "auth/login" not in str(chat.url)
