"""Tests for the static server's kernel-side local-file route (Range support).

The route backs add_raster's local-GeoTIFF support: the in-iframe GeoTIFF
reader needs HTTP Range, which the stdlib static handler does not implement.
"""

from __future__ import annotations

import urllib.error
import urllib.request
from pathlib import Path

import pytest

from geolibre import _server


@pytest.fixture
def served(tmp_path, monkeypatch):
    """Boot the static server against a stub bundle and yield helpers.

    Resets the module-level server singleton so the test owns a fresh server,
    then registers a known-content file and yields its URL plus the payload.
    The server is shut down on teardown so its background thread/socket does not
    leak across the suite.
    """
    # Force a fresh singleton: other tests/imports may have started one already.
    monkeypatch.setattr(_server, "_server", None)
    monkeypatch.setattr(_server, "_base_url", None)
    monkeypatch.setattr(_server, "_port", None)
    monkeypatch.setattr(_server, "_local_files", {})
    monkeypatch.setattr(_server, "_raster_tiles", {})

    bundle = tmp_path / "app"
    bundle.mkdir()
    (bundle / "index.html").write_text("<html></html>", encoding="utf-8")
    _server.serve_app(bundle)

    payload = bytes(range(256)) * 40  # 10_240 deterministic bytes
    raster = tmp_path / "dem.tif"
    raster.write_bytes(payload)
    url = _server.register_local_file(raster)
    try:
        yield url, payload
    finally:
        # serve_app set the module-level singleton to this test's server; stop it
        # before monkeypatch restores the originals.
        server = _server._server
        if server is not None:
            server.shutdown()
            server.server_close()


def _get(url, headers=None):
    request = urllib.request.Request(url, headers=headers or {})
    with urllib.request.urlopen(request, timeout=5) as response:  # noqa: S310 - loopback
        return response.status, dict(response.headers), response.read()


def test_full_get_returns_whole_file(served):
    url, payload = served
    status, headers, body = _get(url)
    assert status == 200
    assert body == payload
    assert headers["Accept-Ranges"] == "bytes"


def test_range_request_returns_206_slice(served):
    url, payload = served
    status, headers, body = _get(url, {"Range": "bytes=100-199"})
    assert status == 206
    assert headers["Content-Range"] == f"bytes 100-199/{len(payload)}"
    assert body == payload[100:200]


def test_suffix_range(served):
    url, payload = served
    status, _headers, body = _get(url, {"Range": "bytes=-50"})
    assert status == 206
    assert body == payload[-50:]


def test_open_ended_range(served):
    url, payload = served
    status, headers, body = _get(url, {"Range": "bytes=10200-"})
    assert status == 206
    assert headers["Content-Range"] == f"bytes 10200-{len(payload) - 1}/{len(payload)}"
    assert body == payload[10200:]


def test_non_bytes_range_unit_serves_the_whole_file(served):
    """An unparsable range unit falls back to a plain 200, with no Content-Range."""
    status, headers, body = _get(served[0], {"Range": "items=0-99"})
    assert status == 200
    assert body == served[1]
    # Content-Range belongs on 206/416 only; emitting it here would contradict
    # the full-file body.
    assert "Content-Range" not in headers


def test_unsatisfiable_range_returns_416(served):
    url, payload = served
    with pytest.raises(urllib.error.HTTPError) as excinfo:
        _get(url, {"Range": "bytes=999999-"})
    assert excinfo.value.code == 416
    assert excinfo.value.headers["Content-Range"] == f"bytes */{len(payload)}"


def test_unknown_token_404s(served):
    url, _payload = served
    base = url.split("/_geolibre_local/", 1)[0]
    with pytest.raises(urllib.error.HTTPError) as excinfo:
        _get(f"{base}/_geolibre_local/deadbeeftoken/x.tif")
    assert excinfo.value.code == 404


def test_register_same_file_reuses_token(served):
    url, _payload = served
    # Re-registering the same file returns the identical URL (no registry growth).
    path = url.split("/_geolibre_local/", 1)[1].split("/", 1)[0]
    again = _server.register_local_file(
        next(p for tok, p in _server._local_files.items() if tok == path)
    )
    assert again == url
    assert len(_server._local_files) == 1


def test_registered_raster_tile_returns_png(served, tmp_path):
    rasterio = pytest.importorskip("rasterio")
    pytest.importorskip("rio_tiler")
    np = pytest.importorskip("numpy")
    from rasterio.transform import from_bounds

    raster = tmp_path / "world.tif"
    with rasterio.open(
        raster,
        "w",
        driver="GTiff",
        width=8,
        height=8,
        count=1,
        dtype="float32",
        crs="EPSG:4326",
        transform=from_bounds(-180, -85, 180, 85, 8, 8),
    ) as dst:
        dst.write(np.arange(64, dtype="float32").reshape(1, 8, 8))

    template = _server.register_raster_tiles(raster, bands=[1], colormap="turbo", rescale=[[0, 63]])
    status, headers, body = _get(template.format(z=0, x=0, y=0))
    assert status == 200
    assert headers["Content-Type"] == "image/png"
    assert body.startswith(b"\x89PNG\r\n\x1a\n")


def test_unregister_local_file_drops_the_token(served, tmp_path):
    url, _payload = served
    assert len(_server._local_files) == 1
    registered = next(iter(_server._local_files.values()))

    _server.unregister_local_file(registered)
    assert _server._local_files == {}

    # The route is gone once the token is dropped.
    with pytest.raises(urllib.error.HTTPError) as excinfo:
        _get(url)
    assert excinfo.value.code == 404


def test_unregister_unknown_file_is_a_no_op(served, tmp_path):
    _server.unregister_local_file(tmp_path / "never-registered.tif")
    assert len(_server._local_files) == 1


def test_options_preflight_allows_range(served):
    url, _payload = served
    request = urllib.request.Request(url, method="OPTIONS")
    with urllib.request.urlopen(request, timeout=5) as response:  # noqa: S310 - loopback
        assert response.status == 200
        assert response.headers["Access-Control-Allow-Origin"] == "*"
        assert "Range" in response.headers["Access-Control-Allow-Headers"]


def test_register_missing_file_raises(served):
    with pytest.raises(ValueError, match="not found"):
        _server.register_local_file(Path("/no/such/raster.tif"))


def test_register_requires_running_server(monkeypatch, tmp_path):
    monkeypatch.setattr(_server, "_base_url", None)
    existing = tmp_path / "f.tif"
    existing.write_bytes(b"x")
    with pytest.raises(RuntimeError, match="not running"):
        _server.register_local_file(existing)