"""ET-013 — integration-тесты endpoint ``/terrain/{layer}/{z}/{x}/{y}.png``
для z9-z11 (REQ-F-15; AC-16).

Тесты используют FastAPI TestClient против ``src.api.main:app``. Реальные
тайлы рельефа в репозиторий не коммитятся (PH-6 data live in ``data/terrain/``
на test-сервере). Поэтому:

* Если директория с тайлами недоступна — тесты ``IT-TILE-*`` помечаются
  ``skipped`` с пояснением.
* Регрессии «404 на невалидный zoom / неизвестный layer» работают всегда —
  они не требуют исходных данных.

Покрытие тест-плана (`04-test-plan.yaml`):
- IT-TILE-Z9-01, IT-TILE-Z10-01, IT-TILE-Z11-01 (по обоим слоям hillshade и tri — см. F-2)
- IT-TILE-INVALID-LAYER, IT-TILE-MISSING
- IT-TILE-CACHE-HEADER
- IT-TILE-TRI-WHITELIST: регрессия, что endpoint признаёт `tri` (см. review F-1)
"""

import os
from pathlib import Path

import pytest
from fastapi.testclient import TestClient

from src.api.main import TERRAIN_DIR, app

REPO_ROOT = Path(__file__).resolve().parents[2]

# Опционально каталог тайлов перекрывается через env. По умолчанию — берём
# тот же путь, что использует api (см. src/api/main.py TERRAIN_DIR).
TERRAIN_ROOT = Path(os.environ.get("TERRAIN_DIR", TERRAIN_DIR))


@pytest.fixture(scope="module")
def client() -> TestClient:
    return TestClient(app)


def _find_sample_tile(layer: str, z: int):
    """Найти любую существующую (x, y) пару тайла для layer/z.

    Возвращает None, если данных нет — тогда вызывающий тест помечается skipped.
    """
    z_dir = TERRAIN_ROOT / layer / str(z)
    if not z_dir.is_dir():
        return None
    for x_dir in sorted(z_dir.iterdir()):
        if not x_dir.is_dir():
            continue
        try:
            x = int(x_dir.name)
        except ValueError:
            continue
        for y_file in sorted(x_dir.iterdir()):
            if y_file.suffix != ".png":
                continue
            try:
                y = int(y_file.stem)
            except ValueError:
                continue
            return (x, y)
    return None


def _maybe_skip(layer: str, z: int):
    sample = _find_sample_tile(layer, z)
    if sample is None:
        pytest.skip(
            f"PH-6 data not present: {TERRAIN_ROOT}/{layer}/{z}/ — "
            "integration smoke skipped (см. TRZ REQ-F-15)."
        )
    return sample


# ──────────────────────────────────────────────────────────────────────────────
# IT-TILE-Z9 / Z10 / Z11 — hillshade и TRI доступны на расширенном диапазоне зумов
# (review F-2: параметризация по layer, чтобы покрыть оба слоя endpoint'а)
# ──────────────────────────────────────────────────────────────────────────────


@pytest.mark.parametrize("layer", ["hillshade", "tri"])
@pytest.mark.parametrize("zoom", [9, 10, 11])
def test_terrain_tile_available_z9_z10_z11(client: TestClient, layer: str, zoom: int):
    """IT-TILE-Z9/Z10/Z11-01: тайл рельефа (hillshade и tri) на z9-z11 отдаётся 200 PNG."""
    x, y = _maybe_skip(layer, zoom)
    resp = client.get(f"/terrain/{layer}/{zoom}/{x}/{y}.png")
    assert resp.status_code == 200, resp.text
    assert resp.headers["content-type"] == "image/png"
    assert len(resp.content) > 0


# ──────────────────────────────────────────────────────────────────────────────
# IT-TILE-TRI-WHITELIST (review F-1) — endpoint признаёт слой `tri`
# Этот тест не зависит от наличия тайлов: для существующего слоя без файла на
# диске мы получаем 404 "Tile not found", а для несуществующего слоя — 404
# "Unknown layer". Различие проверяется по телу ответа.
# ──────────────────────────────────────────────────────────────────────────────


@pytest.mark.parametrize("layer", ["hypso", "hillshade", "tri"])
def test_known_terrain_layer_accepted_by_whitelist(client: TestClient, layer: str):
    """Регрессия F-1: каждый из (hypso, hillshade, tri) проходит whitelist."""
    # x, y заведомо не существуют на диске → должны получить 404 "Tile not found",
    # но НЕ "Unknown layer". Эта проверка работает без локальных PNG-данных.
    resp = client.get(f"/terrain/{layer}/9/999999/999999.png")
    assert resp.status_code == 404
    detail = resp.json().get("detail", "")
    assert detail != "Unknown layer", (
        f"layer={layer!r} должен проходить whitelist, факт detail={detail!r}"
    )
    assert detail == "Tile not found", (
        f"для несуществующего файла ожидался detail='Tile not found', факт={detail!r}"
    )


# ──────────────────────────────────────────────────────────────────────────────
# Регрессии 404 — работают независимо от наличия данных
# ──────────────────────────────────────────────────────────────────────────────


def test_unknown_terrain_layer_returns_404(client: TestClient):
    """IT-TILE-INVALID-LAYER: неизвестный layer → 404 "Unknown layer".

    Парный к ``test_known_terrain_layer_accepted_by_whitelist`` (F-1):
    подтверждает, что whitelist всё ещё отсекает посторонние слои.
    """
    resp = client.get("/terrain/unknown_layer/9/0/0.png")
    assert resp.status_code == 404
    assert resp.json().get("detail") == "Unknown layer"


def test_missing_terrain_tile_returns_404(client: TestClient):
    """IT-TILE-MISSING: hillshade-тайл с нереальными x/y → 404."""
    resp = client.get("/terrain/hillshade/9/999999/999999.png")
    assert resp.status_code == 404


def test_invalid_zoom_returns_404(client: TestClient):
    """Доп. регрессия: zoom вне нарезанного диапазона → 404 (тайла нет на диске)."""
    resp = client.get("/terrain/hillshade/99/0/0.png")
    assert resp.status_code == 404


# ──────────────────────────────────────────────────────────────────────────────
# IT-TILE-CACHE-HEADER — Cache-Control: immutable сохраняется (NFR-03, REQ-F-18)
# ──────────────────────────────────────────────────────────────────────────────


def test_terrain_tile_cache_control_immutable(client: TestClient):
    """IT-TILE-CACHE-HEADER: тайл рельефа отдаётся с Cache-Control: immutable."""
    x, y = _maybe_skip("hillshade", 9)
    resp = client.get(f"/terrain/hillshade/9/{x}/{y}.png")
    assert resp.status_code == 200
    cache_control = resp.headers.get("cache-control", "")
    assert "immutable" in cache_control, f"ожидался immutable в Cache-Control, факт: {cache_control}"
    assert "max-age=31536000" in cache_control, (
        f"ожидался max-age=31536000 в Cache-Control, факт: {cache_control}"
    )
